1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        「一次寫過癮」手寫 Promise 全家桶 + Generator + async/await

        共 34830字,需瀏覽 70分鐘

         ·

        2021-03-01 13:58

        (給全棧前端精選加星標,提升前端技能


        轉(zhuǎn)自:前端食堂

        手寫 Promise 全家桶

        Promise/A+ 規(guī)范[4]鎮(zhèn)樓!

        如果你沒讀過 Promise/A+ 規(guī)范也沒關(guān)系,我?guī)湍憧偨Y(jié)了如下三部分重點:

        不過建議看完本文后還是要親自去讀一讀,不多 bb,開始展示。



        規(guī)范重點

        1.Promise 狀態(tài)

        Promise 的三個狀態(tài)分別是 pending、fulfilled 和 rejected。

        • pending: 待定,Promise 的初始狀態(tài)。在此狀態(tài)下可以落定 (settled) 為 fulfilled 或 rejected狀態(tài)。
        • fulfilled: 兌現(xiàn)(解決),表示執(zhí)行成功。Promise 被 resolve 后的狀態(tài),狀態(tài)不可再改變,且有一個私有的值 value。
        • rejected: 拒絕,表示執(zhí)行失敗。Promise 被 reject 后的狀態(tài),狀態(tài)不可再改變,且有一個私有的原因 reason。

        注意:value 和 reason 也是不可變的,它們包含原始值或?qū)ο蟮牟豢尚薷牡囊茫J值為 undefined。

        2.Then 方法

        要求必須提供一個 then 方法來訪問當前或最終的 value 或 reason。

        promise.then(onFulfilled, onRejected)
        • 1.then 方法接受兩個函數(shù)作為參數(shù),且參數(shù)可選。
        • 2.如果可選參數(shù)不為函數(shù)時會被忽略。
        • 3.兩個函數(shù)都是異步執(zhí)行,會放入事件隊列等待下一輪 tick。
        • 4.當調(diào)用 onFulfilled 函數(shù)時,會將當前 Promise 的 value 值作為參數(shù)傳入。
        • 5.當調(diào)用 onRejected 函數(shù)時,會將當前 Promise 的 reason 失敗原因作為參數(shù)傳入。
        • 6.then 函數(shù)的返回值為 Promise。
        • 7.then 可以被同一個 Promise 多次調(diào)用。

        3.Promise 解決過程

        Promise 的解決過程是一個抽象操作,接收一個 Promise 和一個值 x。

        針對 x 的不同值處理以下幾種情況:

        • 1.x 等于 Promise

        拋出 TypeError 錯誤,拒絕 Promise。

        • 2.x 是 Promise 的實例

        如果 x 處于待定狀態(tài),那么 Promise 繼續(xù)等待直到 x 兌現(xiàn)或拒絕,否則根據(jù) x 的狀態(tài)兌現(xiàn)/拒絕 Promise。

        • 3.x 是對象或函數(shù)

        取出 x.then 并調(diào)用,調(diào)用時將 this 指向 x。將 then 回調(diào)函數(shù)中得到的結(jié)果 y 傳入新的 Promise 解決過程中,遞歸調(diào)用。

        如果執(zhí)行報錯,則將以對應(yīng)的失敗原因拒絕 Promise。

        這種情況就是處理擁有 then() 函數(shù)的對象或函數(shù),我們也叫它 thenable。

        • 4.如果 x 不是對象或函數(shù)

        以 x 作為值執(zhí)行 Promise。

        手寫 Promise

        1.首先定義 Promise 的三個狀態(tài)

        var PENDING = 'pending';
        var FULFILLED = 'fulfilled';
        var REJECTED = 'rejected';

        2.我們再來搞定 Promise 的構(gòu)造函數(shù)

        創(chuàng)建 Promise 時需要傳入 execute 回調(diào)函數(shù),接收兩個參數(shù),這兩個參數(shù)分別用來兌現(xiàn)和拒絕當前 Promise。

        所以我們需要定義 resolve() 和 reject() 函數(shù)。

        初始狀態(tài)為 PENDING,在執(zhí)行時可能會有返回值 value,在拒絕時會有拒絕原因 reason。

        同時需要注意,Promise 內(nèi)部的異常不能直接拋出,需要進行異常捕獲。

        function Promise(execute{
            var that = this;
            that.state = PENDING;
            function resolve(value{
                if (that.state === PENDING) {
                    that.state = FULFILLED;
                    that.value = value;
                }
            }
            function reject(reason{
                if (that.state === PENDING) {
                    that.state = REJECTED;
                    that.reason = reason;
                }
            }
            try {
                execute(resolve, reject);
            } catch (e) {
                reject(e);
            }
        }

        3. 實現(xiàn) then() 方法

        then 方法用來注冊當前 Promise 狀態(tài)落定后的回調(diào),每個 Promise 實例都需要有它,顯然要寫到 Promise 的原型 prototype 上,并且 then() 函數(shù)接收兩個回調(diào)函數(shù)作為參數(shù),分別是 onFulfilled 和 onRejected。

        Promise.prototype.then = function(onFulfilled, onRejected{}

        根據(jù)上面第 2 條規(guī)則,如果可選參數(shù)不為函數(shù)時應(yīng)該被忽略,我們要對參數(shù)進行如下判斷。

        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function(xreturn x; }
        onRejected = typeof onRejected === 'function' ? onRejected : function(ethrow e; }

        根據(jù)第 3 條規(guī)則,需要使用 setTimeout 延遲執(zhí)行,模擬異步。

        根據(jù)第 4 條、第 5 條規(guī)則,需要根據(jù) Promise 的狀態(tài)來執(zhí)行對應(yīng)的回調(diào)函數(shù)。

        在 PENDING 狀態(tài)時,需要等到狀態(tài)落定才能調(diào)用。我們可以將 onFulfilled 和 onRejected 函數(shù)存到 Promise 的屬性 onFulfilledFn 和 onRejectedFn 中,

        當狀態(tài)改變時分別調(diào)用它們。

        var that = this;
        var promise;
        if (that.state === FULFILLED) {
            setTimeout(function() {
                onFulfilled(that.value);
            });
        }
        if (that.state === REJECTED) {
            setTimeout(function() {
                onRejected(that.reason);
            });
        }
        if (that.state === PENDING) {
             that.onFulfilledFn = function() {
                onFulfilled(that.value);
            }
            that.onRejectedFn = function() {
                onRejected(that.reason);
            }
        }

        根據(jù)第 6 條規(guī)則,then 函數(shù)的返回值為 Promise,我們分別給每個邏輯添加并返回一個 Promise。

        同時,then 支持鏈式調(diào)用,我們需要將 onFulfilledFn 和 onRejectedFn 改成數(shù)組。

        var that = this;
        var promise;
        if (that.state === FULFILLED) {
            promise = new Promise(function(resolve, reject{
                setTimeout(function() {
                    try {
                        onFulfilled(that.value);
                    } catch (reason) {
                        reject(reason);
                    }
                });
            });
        }
        if (that.state === REJECTED) {
            promise = new Promise(function(resolve, reject{
                setTimeout(function() {
                    try {
                        onRejected(that.reason);
                    } catch (reason) {
                        reject(reason);
                    }
                });
            });
        }
        if (that.state === PENDING) {
            promise = new Promise(function(resolve, reject{
                that.onFulfilledFn.push(function() {
                    try {
                        onFulfilled(that.value);
                    } catch (reason) {
                        reject(reason);
                    }
                })
                that.onRejectedFn.push(function() {
                    try {
                        onRejected(that.reason);
                    } catch (reason) {
                        reject(reason);
                    }
                });
            });
        }

        與上面相對應(yīng)的,再將 Promise 的構(gòu)造函數(shù)相應(yīng)的進行改造。

        • 1.添加 onFulFilledFn 和 onRejectedFn 數(shù)組。

        • 2.resolve() 和 reject() 函數(shù)改變狀態(tài)時,需要異步調(diào)用數(shù)組中的函數(shù),同樣使用 setTimeout 來模擬異步。

        function Promise(execute{
            var that = this;
            that.state = PENDING;
            that.onFulfilledFn = [];
            that.onRejectedFn = [];

            function resolve(value{
                setTimeout(function() {
                    if (that.state === PENDING) {
                        that.state = FULFILLED;
                        that.value = value;
                        that.onFulfilledFn.forEach(function(fn{
                            fn(that.value);
                        })
                    }
                })
            }
            function reject(reason{
                setTimeout(function() {
                    if (that.state === PENDING) {
                        that.state = REJECTED;
                        that.reason = reason;
                        that.onRejectedFn.forEach(function(fn{
                            fn(that.reason);
                        })
                    }
                })
            }
            try {
                execute(resolve, reject);
            } catch (e) {
                reject(e);
            }
        }

        4.Promise 解決過程 resolvePromise()

        Promise 解決過程分為以下幾種情況,我們需要分別進行處理:

        1.x 等于 Promise TypeError 錯誤

        此時相當于 Promise.then 之后 return 了自己,因為 then 會等待 return 后的 Promise,導(dǎo)致自己等待自己,一直處于等待。。

        function resolvePromise(promise, x{
            if (promise === x) {
                return reject(new TypeError('x 不能等于 promise'));
            }
        }

        2.x 是 Promise 的實例

        如果 x 處于待定狀態(tài),Promise 會繼續(xù)等待直到 x 兌現(xiàn)或拒絕,否則根據(jù) x 的狀態(tài)兌現(xiàn)/拒絕 Promise。

        我們需要調(diào)用 Promise 在構(gòu)造時的函數(shù) resolve() 和 reject() 來改變 Promise 的狀態(tài)。

        function resolvePromise(promise, x, resolve, reject{
            // ...
            if (x instanceof Promise) {
                if (x.state === FULFILLED) {
                    resolve(x.value);
                } else if (x.state === REJECTED) {
                    reject(x.reason);
                } else {
                    x.then(function(y{
                        resolvePromise(promise, y, resolve, reject);
                    }, reject);
                }
            }
        }

        3.x 是對象或函數(shù)

        取出 x.then 并調(diào)用,調(diào)用時將 this 指向 x,將 then 回調(diào)函數(shù)中得到的結(jié)果 y 傳入新的 Promise 解決過程中,遞歸調(diào)用。

        如果執(zhí)行報錯,則將以對應(yīng)的失敗原因拒絕 Promise。

        x 可能是一個 thenable 而非真正的 Promise。

        需要設(shè)置一個變量 executed 避免重復(fù)調(diào)用。

        function resolvePromise(promise, x, resolve, reject{
            // ...
            if ((x !== null) && ((typeof x === 'object' || (typeof x === 'function'))) {
                var executed;
                try {
                    var then = x.then;
                    if (typeof then === 'function') {
                        then.call(x, function(y{
                            if (executed) return;
                            executed = true;
                            return resolvePromise(promise, y, resolve, reject);
                        }, function (e{
                            if (executed) return;
                            executed = true;
                            reject(e);
                        }) 
                    } else {
                        resolve(x);
                    }
                } catch (e) {
                    if (executed) return;
                    executed = true;
                    reject(e);
                }
            }
        }

        4.直接將 x 作為值執(zhí)行

        function resolvePromise(promise, x, resolve, reject{
            // ...
            resolve(x)
        }

        測試

        // 為了支持測試,將模塊導(dǎo)出
        module.exports = {
          deferred() {
            var resolve;
            var reject;
            var promise = new Promise(function (res, rej{
              resolve = res;
              reject = rej;
            })
            return {
              promise,
              resolve,
              reject
            }
          }
        }

        我們可以選用這款測試工具對我們寫的 Promise 進行測試 Promise/A+ 測試工具: promises-aplus-tests[5]

        目前支持 827 個測試用例,我們只需要在導(dǎo)出模塊的時候遵循 CommonJS 規(guī)范,按照要求導(dǎo)出對應(yīng)的函數(shù)即可。



        Promise.resolve

        Promise.resolve() 可以實例化一個解決(fulfilled) 的 Promise。

        Promise.resolve = function(value{
            if (value instanceof Promise) {
                return value;
            }

            return new Promise(function(resolve, reject{
                resolve(value);
            });
        }

        Promise.reject

        Promise.reject() 可以實例化一個 rejected 的 Promise 并拋出一個異步錯誤(這個錯誤不能通過try/catch捕獲,只能通過拒絕處理程序捕獲)

        Promise.reject = function(reason{
            return new Promise(function(resolve, reject{
                reject(reason);
            });
        }

        Promise.prototype.catch

        Promise.prototype.catch() 方法用于給 Promise 添加拒絕時的回調(diào)函數(shù)。

        Promise.prototype.catch = function(onRejected{
            return this.then(null, onRejected);
        }

        Promise.prototype.finally

        Promise.prototype.finally() 方法用于給 Promise 添加一個不管最終狀態(tài)如何都會執(zhí)行的操作。

        Promise.prototype.finally = function(fn{
            return this.then(function(value{
                return Promise.resolve(value).then(function() {
                    return value;
                });
            }, function(error{
                return Promise.resolve(reason).then(function() {
                    throw error;
                });
            });
        }

        Promise.all

        Promise.all() 方法會將多個 Promise 實例組合成一個新的 Promise 實例。

        組合后的 Promise 實例只有當每個包含的 Promise 實例都解決(fulfilled)后才解決(fulfilled),如果有一個包含的 Promise 實例拒絕(rejected)了,則合成的 Promise 也會拒絕(rejected)。

        兩個注意點:

        • 傳入的是可迭代對象,用 for...of 遍歷 Iterable 更安全
        • 傳入的每個實例不一定是 Promise,需要用 Promise.resolve() 包裝
        Promise.all = function(promiseArr{
            return new Promise(function(resolve, reject{
                const length = promiseArr.length;
                const result = [];
                let count = 0;
                if (length === 0) {
                    return resolve(result);
                }

                for (let item of promiseArr) {
                    Promise.resolve(item).then(function(data{
                        result[count++] = data;
                        if (count === length) {
                            resolve(result);
                        }
                    }, function(reason{
                        reject(reason);
                    });
                }
            });
        }

        Promise.race

        Promise.race() 同樣返回一個合成的 Promise 實例,其會返回這一組中最先解決(fulfilled)或拒絕(rejected)的 Promise 實例的返回值。

        Promise.race = function(promiseArr{
            return new Promise(function(resolve, reject{
                const length = promiseArr.length;
                if (length === 0) {
                    return resolve();
                } 

                for (let item of promiseArr) {
                    Promise.resolve(item).then(function(value{
                        return resolve(value);
                    }, function(reason{
                        return reject(reason);
                    });
                }
            });
        }

        Promise.any

        Promise.any() 相當于 Promise.all() 的反向操作,同樣返回一個合成的 Promise 實例,只要其中包含的任何一個 Promise 實例解決(fulfilled)了,合成的 Promise 就解決(fulfilled)。

        只有當每個包含的 Promise 都拒絕(rejected)了,合成的 Promise 才拒絕(rejected)。



        Promise.any = function(promiseArr{
            return new Promise(function(resolve, reject{
                const length = promiseArr.length;
                const result = [];
                let count = 0;
                if (length === 0) {
                    return resolve(result);
                } 

                for (let item of promiseArr) {
                    Promise.resolve(item).then((value) => {
                        return resolve(value);
                    }, (reason) => {
                        result[count++] = reason;
                        if (count === length) {
                            reject(result);
                        }
                    });
                }
            });
        }

        Promise.allSettled

        Promise.allSettled() 方法也是返回一個合成的 Promise,不過只有等到所有包含的每個 Promise 實例都返回結(jié)果落定時,不管是解決(fulfilled)還是拒絕(rejected),合成的 Promise 才會結(jié)束。一旦結(jié)束,狀態(tài)總是 fulfilled。

        其返回的是一個對象數(shù)組,每個對象表示對應(yīng)的 Promise 結(jié)果。

        對于每個結(jié)果對象,都有一個 status 字符串。如果它的值為 fulfilled,則結(jié)果對象上存在一個 value 。如果值為 rejected,則存在一個 reason 。

        Promise.allSettled = function(promiseArr{
          return new Promise(function(resolve{
            const length = promiseArr.length;
            const result = [];
            let count = 0;

            if (length === 0) {
              return resolve(result);
            } else {
              for (let item of promiseArr) {
                Promise.resolve(item).then((value) => {
                    result[count++] = { status'fulfilled'value: value };
                    if (count === length) {
                        return resolve(result);
                    }
                }, (reason) => {
                    result[count++] = { status'rejected'reason: reason };
                    if (count === length) {
                        return resolve(result);
                    }
                });
              }
            }
          });
        }


        // 使用 Promise.finally 實現(xiàn)
        Promise.allSettled = function(promises{
            // 也可以使用擴展運算符將 Iterator 轉(zhuǎn)換成數(shù)組
            // const promiseArr = [...promises]
            const promiseArr = Array.from(promises)
            return new Promise(resolve => {
                const result = []
                const len = promiseArr.length;
                let count = len;
                if (len === 0) {
                  return resolve(result);
                }
                for (let i = 0; i < len; i++) {
                    promiseArr[i].then((value) => {
                        result[i] = { status'fulfilled'value: value };
                    }, (reason) => {
                        result[i] = { status'rejected'reason: reason };
                    }).finally(() => { 
                        if (!--count) {
                            resolve(result);
                        }
                    });
                }
            });
        }



        // 使用 Promise.all 實現(xiàn)
        Promise.allSettled = function(promises{
            // 也可以使用擴展運算符將 Iterator 轉(zhuǎn)換成數(shù)組
            // const promiseArr = [...promises]
            const promiseArr = Array.from(promises)
            return Promise.all(promiseArr.map(p => Promise.resolve(p).then(res => {
              return { status'fulfilled'value: res }
            }, error => {
              return { status'rejected'reason: error }
            })));
        };

        手寫 Generator 函數(shù)

        先來簡單回顧下 Generator 的使用:

        functionwebCanteenGenerator() {
            yield '店小二兒,給我切兩斤牛肉來';
            yield '再來十八碗酒';
            return '好酒!這酒有力氣!';
        }

        var canteen = webCanteenGenerator();
        canteen.next();
        canteen.next();
        canteen.next();
        canteen.next();

        // {value: "店小二兒,給我切兩斤牛肉來", done: false}
        // {value: "再來十八碗酒", done: false}
        // {value: "好酒!這酒有力氣!", done: true}
        // {value: undefined, done: true}



        // 簡易版
        // 定義生成器函數(shù),入?yún)⑹侨我饧?/span>
        function webCanteenGenerator(list{
            var index = 0;
            var len = list.length;
            return {
                // 定義 next 方法
                // 記錄每次遍歷位置,實現(xiàn)閉包,借助自由變量做迭代過程中的“游標”
                nextfunction() {
                    var done = index >= len; // 如果索引還沒有超出集合長度,done 為 false
                    var value = !done ? list[index++] : undefined// 如果 done 為 false,則可以繼續(xù)取值
                    // 返回遍歷是否完畢的狀態(tài)和當前值
                    return {
                        done: done,
                        value: value
                    }
                }
            }
        }

        var canteen = webCanteenGenerator(['道路千萬條''安全第一條''行車不規(guī)范']);
        canteen.next();
        canteen.next();
        canteen.next();

        // {done: false, value: "道路千萬條"}
        // {done: false, value: "安全第一條"}
        // {done: false, value: "行車不規(guī)范"}
        // {done: true, value: undefined}



        手寫 async/await

        Generator 缺陷:

        • 1.函數(shù)外部無法捕獲異常
        • 2.多個 yield 會導(dǎo)致調(diào)試困難

        async 函數(shù)對 Generator 函數(shù)改進如下:

        • 1.內(nèi)置執(zhí)行器
        • 2.更好的語義
        • 3.更廣的適用性
        • 4.返回值是 Promise

        async/await 做的事情就是將 Generator 函數(shù)轉(zhuǎn)換成 Promise,說白了,async 函數(shù)就是 Generator 函數(shù)的語法糖,await 命令就是內(nèi)部 then 命令的語法糖。

        const fetchData = (data) => new Promise((resolve) => setTimeout(resolve, 1000, data + 1))

        const fetchResult = async function () {
            var result1 = await fetchData(1);
            var result2 = await fetchData(result1);
            var result3 = await fetchData(result2);
            console.log(result3);
        }

        fetchResult();

        可以嘗試通過 Babel[8] 官網(wǎng)轉(zhuǎn)換一下上述代碼,可以看到其核心就是 _asyncToGenerator 方法。

        我們下面來實現(xiàn)它。

        function asyncToGenerator(generatorFn{
            // 將 Generator 函數(shù)包裝成了一個新的匿名函數(shù),調(diào)用這個匿名函數(shù)時返回一個 Promise
            return function() {
                // 生成迭代器,相當于執(zhí)行 Generator 函數(shù)
                // 如上面三碗不過崗例子中的 var canteen = webCanteenGenerator()
                var gen = generatorFn.apply(thisarguments);
                return new Promise(function(resolve, reject{
                    // 利用 Generator 分割代碼片段,每一個 yield 用 Promise 包裹起來
                    // 遞歸調(diào)用 Generator 函數(shù)對應(yīng)的迭代器,當?shù)鲌?zhí)行完成時執(zhí)行當前的 Promise,失敗時則拒絕 Promise
                    function step(key, arg{
                        try {
                            var info = gen[key](arg "key");
                            var value = info.value;
                        } catch (error) {
                            reject(error);
                            return;
                        }

                        if (info.done) {
                            // 遞歸終止條件,完成了就 resolve
                            resolve(value);
                        } else {
                            return Promise.resolve(value).then(function(value{
                                step('next', value);
                            }, function(err{
                                step('throw', err);
                            });
                        }
                    }
                    return step('next');
                });
            }
        }

        好了,本文到這里就告一段落,如果上述代碼你發(fā)現(xiàn)有問題的地方,可以在評論區(qū)留言,一起探討學(xué)習(xí)。

        瀏覽 51
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            在线观看黄色豆花视频免费网站 | 3D动漫精品啪啪一区二区免费 | 一级成人黄色 | 日韩无码视频免费 | 护士在办公室里被躁在线视频 | 艹久久 | 保守娇妻被开发调教经历 | 亚洲精品乱码久久久久久蜜桃图片 | 337p大胆啪啪私拍人体 | 欧美交换配乱婬粗大嫩模 |