面接官: 約束は手書きで書いてください

フロントエンド面接クエスチョンバンク(面接に必要)オススメ度:★★★★★            

宛先:フロントエンド面接の質問バンク

序文

インタビュアー: 手書きで書いてくださいPromise(話の本題に入る)

私:そうは言ってもPromise、まずはJavaScript异步编程会社の発展の歴史を紹介して、なぜそうなったのPromise会出现かを理解してもらう必要がありますPromise解决了什么问题

  • フェーズ 1: コールバック関数
  • フェーズ 2: イベントのパブリッシュ/サブスクライブ モデル
  • ...

インタビュアー: 非同期プログラミングの開発履歴は気にしません (せっかちです)。最近ではPromiseそれが解決された問題であることは誰もが知っています。私が気にしているのはあなたのことです。あなたは直接書きます! 回调地狱编码能力show you code

私:分かった!(実際、コードを手書きするのは私の得意分野です、ヒヒ!)

手書きの約束

次の 3 つの状態について説明しますpromise

  • PENDING: 待機状態、約束の初期状態
  • FULFILLED: 成功状態。関数呼び出し後、Promiseは に変わりresolveますPENDING等待态FULFILLED成功态
  • REJECTED:failed 状態: rejectPromise が関数を呼び出した後、次のようにPENDING等待态変化します。REJECTED失败态

知らせ:

  1. Promise の状態が一度変更されると、再度変更することはできません。たとえば、変更から にresolve電話をかけた場合、その状態は常に となり、変更から通話拒否に変更することはできません。PEDINGFULFILLEDFULFILLEDFULFILLEDREJECTED
  2. 状態はまたはからのみPENDING変更できまたはから戻ることはできません。これも理解しやすいですが、状態は前進のみでき、後退はできません。FULFILLEDREJECTEDFULFILLEDREJECTEDPENDING

まずは使い方を見てみましょう:

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方法resolverejectthenPromise成功的回调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 つの配列に格納されているコールバック関数を実行します成功的回调失败的回调resolvereject

インタビュアー: はい、約束の連鎖はどのようにして実現されるのですか?

例: 次のコード:

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)
})

出力されます:

画像.png

これはどのようにして達成されるのでしょうか?

私: これも簡単で、内部でメソッドを呼び出すとthen新しいメソッドが返されpromise、その新しいメソッドpromiseに次のメソッドを引き継がせますthen

注: ここで戻ることはできません。これにより、複数のメソッドがすべて同じ制御下に置かれることthisになりますthenpromise

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)
    }
}
  1. まず、新しく返されたPromiseがpromise2等しいかどうかを判断しxUnhandledPromiseRejectionWarning: TypeError: Chaining cycle detected for promise #<Promise>内部循環参照を防ぐためにエラーをスローします。
  2. 変数の宣言は、無限ループを防ぐcalledために成功または失敗のコールバックを 1 つだけ呼び出せるようにロックを追加することと同じですpromise
  3. objectx の型が であり、でない場合null、または a でメソッドがある場合函数thenx を解析します。promise
  4. then再度呼び出す再帰的解析resolvePromise

手書きラスト

これは内部ではマイクロタスクですが、単純にシミュレーションを渡すことができるpromiseためですEventLoopsetTimout

次に、いくつかのエラー報告キャプチャ コード、いくつかのパラメータ互換コード、および実装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)
    }
}

  1. executorユーザーから渡された関数の実行時にエラーが直接報告されるのを防ぐために、実行中に増加しますtry catch。このとき、直接reject約束する必要があります。
  2. onFulfilledと を呼び出す場合はonRejectedラッピングが必要ですsetTimeoutはい、これで完了です。最後に、書いた内容がpromise仕様に準拠しているかテストしてみましょう。
  3. catchこの関数は実際にthen内部のメソッドを呼び出し、最初のパラメータが渡されますnull

テストの約束

promisePromises/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-testsnpm でグローバルにインストールし、コマンドを使用してpromises-aplus-tests 文件名テストできます。872その中にはテスト ケースがあり、すべてに合格すれば標準と見なされますpromise

画像.png

完璧です。ついに面接官があなたに親指を立てました。

フロントエンド面接クエスチョンバンク(面接に必要)オススメ度:★★★★★            

宛先:フロントエンド面接の質問バンク

おすすめ

転載: blog.csdn.net/weixin_42981560/article/details/132480027