フロントエンド面接クエスチョンバンク(面接に必要)オススメ度:★★★★★
序文
インタビュアー: 手書きで書いてくださいPromise
。(話の本題に入る)
私:そうは言ってもPromise
、まずはJavaScript异步编程
会社の発展の歴史を紹介して、なぜそうなったのPromise会出现
かを理解してもらう必要がありますPromise解决了什么问题
。
- フェーズ 1: コールバック関数
- フェーズ 2: イベントのパブリッシュ/サブスクライブ モデル
- ...
インタビュアー: 非同期プログラミングの開発履歴は気にしません (せっかちです)。最近ではPromise
それが解決された問題であることは誰もが知っています。私が気にしているのはあなたのことです。あなたは直接書きます! !!回调地狱
编码能力
show you code
私:分かった!(実際、コードを手書きするのは私の得意分野です、ヒヒ!)
手書きの約束
次の 3 つの状態について説明しますpromise
。
PENDING
: 待機状態、約束の初期状態FULFILLED
: 成功状態。関数呼び出し後、Promiseは に変わりresolve
ます。PENDING等待态
FULFILLED成功态
REJECTED
:failed 状態:reject
Promise が関数を呼び出した後、次のようにPENDING等待态
変化します。REJECTED失败态
知らせ:
- Promise の状態が一度変更されると、再度変更することはできません。たとえば、変更から に
resolve
電話をかけた場合、その状態は常に となり、変更から通話拒否に変更することはできません。PEDING
FULFILLED
FULFILLED
FULFILLED
REJECTED
- 状態はまたはからのみ
PENDING
変更でき、またはから戻ることはできません。これも理解しやすいですが、状態は前進のみでき、後退はできません。FULFILLED
REJECTED
FULFILLED
REJECTED
PENDING
まずは使い方を見てみましょう:
const p = new Promise((resolve, reject) => {
resolve(111);
})
p.then((value) => {
console.log(value)
}, (error) => {
console.log(error)
})
まず第一に、それをPromise
使用できるようにクラスにする必要がありますnew
。次にPromise实例化
コールバックを渡してメソッドと呼びます。Promiseはこれをexecutor
内部的に実行し、呼び出しパラメーターとして 2 つの関数を渡します。 Promise クラスのプロトタイプ 上記で提供されるメソッドがあり、2 つのコールバック、つまりとを渡すことができます。呼び出し後は にウォークインし、呼び出し後には にウォークインします。立即调用
executor方法
resolve
reject
then
Promise成功的回调
Promise失败的回调
resolve
成功的回调中
reject
失败的回调中
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class Promise {
constructor(executor) {
this.value = undefined
this.reason = undefined
this.status = PENDING
const resolve = (value) => {
if (this.status === PENDING) {
this.value = value
this.status = FULFILLED
this.onResolvedCallbacks.forEach(fn => fn())
}
}
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason
this.status = REJECTED
this.onRejectedCallbacks.forEach(fn => fn())
}
}
executor(resolve, reject);
}
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled && onFulfilled(this.value)
}
if (this.status === REJECTED) {
onRejected && onRejected(this.reason)
}
}
}
module.exports = Promise;
インタビュアー: resovle または拒否が非同期で呼び出された場合はどうなりますか?
私: 簡単です。2 つの配列をキューとして使用して、then
内部にコールバックを格納します。
class Promise {
constructor(executor) {
// ...
// 定义两个数组
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.status === PENDING) {
this.value = value
this.status = FULFILLED
this.onResolvedCallbacks.forEach(fn => fn())
}
}
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason
this.status = REJECTED
this.onRejectedCallbacks.forEach(fn => fn())
}
}
// 默认执行executor函数,并传入resolve和reject函数
executor(resolve, reject)
}
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled && onFulfilled(this.value)
}
if (this.status === REJECTED) {
onRejected && onRejected(this.reason)
}
if (this.status === PENDING) {
this.onResolvedCallbacks.push(() => {
onFulfilled(this.value)
})
this.onRejectedCallbacks.push(() => {
onRejected(this.reason)
})
}
}
}
ここでは2 つの配列onResolvedCallbacks
と が定義されており、onRejectedCallbacks
合計を then にそれぞれ格納し、と を呼び出すときにループ内の 2 つの配列に格納されているコールバック関数を実行します。成功的回调
失败的回调
resolve
reject
インタビュアー: はい、約束の連鎖はどのようにして実現されるのですか?
例: 次のコード:
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(111)
}, 1000)
})
p.then((value1) => {
console.log('value1', value1)
return 222
}, (error1) => {
console.log('error1', error1)
}).then((value2) => {
console.log('value2', value2)
}, (error2) => {
console.log('error2', error2)
})
出力されます:
これはどのようにして達成されるのでしょうか?
私: これも簡単で、内部でメソッドを呼び出すとthen
新しいメソッドが返されpromise
、その新しいメソッドpromise
に次のメソッドを引き継がせますthen
。
注: ここで戻ることはできません。これにより、複数のメソッドがすべて同じ制御下に置かれること
this
になります。then
promise
class Promise {
// ...
then(onFulfilled, onRejected) {
const promise2 = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
// onFulfilled方法可能返回值或者promise
const x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
}
if (this.status === REJECTED) {
// onRejected方法可能返回值或者promise
const x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
}
if (this.status === PENDING) {
this.onResolvedCallbacks.push(() => {
const x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
})
this.onRejectedCallbacks.push(() => {
const x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
})
}
})
return promise2
}
}
中心となるのは、resolvePromise
それが何をするかを見ることです。
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('UnhandledPromiseRejectionWarning: TypeError: Chaining cycle detected for promise #<Promise>'))
}
let called
// 判断x的类型 x是对象或函数才有可能是一个promise
if (typeof x === 'object' && x !== null || typeof x === 'function') {
try {
const then = x.then
if (typeof then === 'function') {
// 只能认为它是一个promise
then.call(x, (y) => {
if (called) return
called = true
resolvePromise(promise2, y, resolve, reject)
}, (r) => {
if (called) return
called = true
reject(r)
})
}else {
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
resolve(x)
}
}
- まず、新しく返されたPromiseが
promise2
等しいかどうかを判断しx
、UnhandledPromiseRejectionWarning: TypeError: Chaining cycle detected for promise #<Promise>
内部循環参照を防ぐためにエラーをスローします。 - 変数の宣言は、無限ループを防ぐ
called
ために成功または失敗のコールバックを 1 つだけ呼び出せるようにロックを追加することと同じですpromise
。 object
x の型が であり、でない場合null
、または a でメソッドがある場合函数
、then
x を解析します。promise
then
再度呼び出す再帰的解析resolvePromise
手書きラスト
これは内部ではマイクロタスクですが、単純にシミュレーションを渡すことができるpromise
ためです。EventLoop
setTimout
次に、いくつかのエラー報告キャプチャ コード、いくつかのパラメータ互換コード、および実装catch
メソッドを追加します。
class Promise {
constructor(executor) {
// ...
// 这里增加try catch
try {
executor(this.resolve, this.reject)
} catch (e) {
reject(e)
}
}
then(onFulfilled, onRejected) {
// 这里兼容下 onFulfilled 和 onRejected 的传参
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
onRejected = typeof onRejected === 'function' ? onRejected : err => {
throw err
}
const promise2 = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
// 用 setTimeout 模拟异步
setTimeout(() => {
try {
const x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
}
if (this.status === REJECTED) {
// 用 setTimeout 模拟异步
setTimeout(() => {
try {
const x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
}
if (this.status === PENDING) {
this.onResolvedCallbacks.push(() => {
// 用 setTimeout 模拟异步
setTimeout(() => {
try {
const x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
this.onRejectedCallbacks.push(() => {
// 用 setTimeout 模拟异步
setTimeout(() => {
try {
const x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
}
})
return promise2
}
// catch函数实际上里面就是调用了then方法
catch (errCallback) {
return this.then(null, errCallback)
}
}
executor
ユーザーから渡された関数の実行時にエラーが直接報告されるのを防ぐために、実行中に増加しますtry catch
。このとき、直接reject
約束する必要があります。onFulfilled
と を呼び出す場合はonRejected
ラッピングが必要ですsetTimeout
。はい、これで完了です。最後に、書いた内容がpromise
仕様に準拠しているかテストしてみましょう。catch
この関数は実際にthen
内部のメソッドを呼び出し、最初のパラメータが渡されますnull
。
テストの約束
promise
Promises/A+という仕様があり、スクリプトを実行して、promise
記述が仕様に準拠しているかどうかをテストできます。
まず、次のコードを に追加する必要がありますpromise
。
// 测试脚本
Promise.defer = Promise.deferred = function () {
let dfd = {}
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
次に、promises-aplus-tests
パッケージをインストールします。たとえば、コマンドを使用してnpm install -g promises-aplus-tests
npm でグローバルにインストールし、コマンドを使用してpromises-aplus-tests 文件名
テストできます。872
その中にはテスト ケースがあり、すべてに合格すれば標準と見なされますpromise
。
完璧です。ついに面接官があなたに親指を立てました。