国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Promise——從閱讀文檔到簡單實現(二)

dinfer / 3587人閱讀

摘要:在和方法執行的時候訂閱事件,將自己的回調函數綁定到事件上,屬性是發布者,一旦它的值發生改變就發布事件,執行回調函數。實現和方法的回調函數都是,當滿足條件對象狀態改變時,這些回調會被放入隊列。所以我需要在某個變為時,刪除它們綁定的回調函數。

前言

按照文檔說明簡單地實現 ES6 Promise的各個方法并不難,但是Promise的一些特殊需求實現起來并不簡單,我首先提出一些不好實現或者容易忽略的需求:

數據傳遞

回調綁定

將回調變成 microtask

實現 then/finally 返回的pending promise “跟隨”它們的回調返回的pending promise

實現 resolve 返回的 promise “跟隨”它的thenable對象參數

實現框架

在解決上述問題前,我們先實現一個框架。
首先,我的目的是實現一個Promise插件,它包括:

構造函數:Promise

靜態方法:resolve、reject、all 和 race

實例方法:then、catch 和 finally

私有函數:identity、thrower 和 isSettled 等

如下:

;(function() {
    function Promise(executor) {
    }
    
    Object.defineProperties(Promise, {
        resolve: {
            value: resolve,
            configurable: true,
            writable: true
        },
        reject: {
            value: reject,
            configurable: true,
            writable: true
        },
        race: {
            value: race,
            configurable: true,
            writable: true
        },
        all: {
            value: all,
            configurable: true,
            writable: true
        }
    });
    
    Promise.prototype = {
        constructor: Promise,
        
        then: function(onFulfilled, onRejected) {
        },
        
        catch: function(onRejected) {
        },
        
        finally: function(onFinally) {
        },
    }
    
    function identity(value) {
        return value;
    }
    
    function thrower(reason) {
        throw reason;
    }
    
    function isSettled(pro) {
        return pro instanceof Promise ? pro.status === "fulfilled" || pro.status === "rejected" : false;
    }
    
    window.Promise = Promise;
})();
解決問題

接下來,我們解決各個問題。

數據傳遞

為了傳遞數據——回調函數需要用到的參數以及 promise 的狀態,我們首先在構造函數Promise中給新生成的對象添加statusvaluereason屬性,并且在構造函數中執行 executor 函數:

function Promise(executor) {
    var self = this;

    this.status = "pending";
    this.value = undefined;
    this.reason = undefined;

    typeof executor === "function" ? executor.call(null,
    function(value) {
        self.value = value;
        self.status = "fulfilled";
    },
    function(reason) {
        self.reason = reason;
        self.status = "rejected";
    }) : false;
}

我們將 value、reason 和 status 保存在 Promise 對象中,這樣,我們就可以在 Promise 對象的方法中通過this(即 Promise 對象的引用)來訪問這些數據,并將其用作回調函數的參數。

按照文檔說明,為了實現鏈式調用,Promise的所有方法都會返回一個 Promise 對象,而且除了Promise.resolve(peomiseObj) 這種情況外都是新生成的 Promise 對象。所以接下來我的大部分方法都會返回一個新的 promise 對象。不生成新對象的特例:

var a = Promise.resolve("a"),
    b = Promise.resolve(a);
console.log(a === b)    //true
回調綁定

接下來,我們要將thencatchfinally中的回調方法綁定到Promise對象的狀態改變這個事件上。
我想到的第一個事件就是onchange事件,但是 promiseObj.status 屬性上并沒有change事件。但是,我馬上想到每次設置accessor屬性的值時,就會調用 accessor 屬性的setter方法。那么,我只要把status屬性設置為存取屬性,然后在它的 setter 方法里觸發綁定的回調函數就行啦!如下:

function Promise(executor) {
    var self = this;

    //存儲狀態的私有屬性
    this._status = "pending";

    this.value = undefined;
    this.reason = undefined;
    //this.events = new Events();

    //存儲狀態的公開屬性
    Object.defineProperty(this, "status", {
        get: function() {
            return self._status;
        },
        set: function(newValue) {
            self._status = newValue;
            //self.events.fireEvent("change");
        },
        configurable: true
    });

    typeof executor === "function" ? executor.call(null,
    function(value) {
        self.value = value;
        self.status = "fulfilled";
    },
    function(reason) {
        self.reason = reason;
        self.status = "rejected";
    }) : false;
}

為了綁定回調函數,我使用了發布訂閱模式。在thencatchfinally方法執行的時候訂閱事件change,將自己的回調函數綁定到change事件上,promiseObj.status 屬性是發布者,一旦它的值發生改變就發布change事件,執行回調函數。
為了節省篇幅,不那么重要的發布者Events() 構造函數及其原型我就不貼代碼了,文章末尾我會給出源代碼。

實現 microtask

thencatchfinally方法的回調函數都是microtask,當滿足條件(promise 對象狀態改變)時,這些回調會被放入microtask隊列。每當調用棧中的macrotask執行完畢時,立刻執行microtask隊列中所有的microtask,這樣一次事件循環就結束了,js引擎等待下一次循環。
要我實現microtask我是做不到的,我就只能用macrotask模仿一下microtask了。
我用 setTimeout 發布的macrotask進行模仿:

Object.defineProperty(this, "status", {
    get: function() {
        return self._status;
    },
    set: function(newValue) {
        self._status = newValue;
        setTimeout(() => {
            self.events.fireEvent("change");
        },
        0);
    },
    configurable: true
});
實現函數

接下來,我們實現各個函數和方法。在知道方法的參數和返回值后再實現方法如有神助,而實現過程中最難處理的就是 pending 狀態的 promise 對象,因為我們要等它變成其它狀態時,再做真正的處理。下面我拿出兩個最具代表性的方法來分析。

靜態方法all

如果忘記了 Promise.all(iterable) 的參數和返回值,可以返回我上一篇文章查看。

function all(iterable) {
    //如果 iterable 不是一個可迭代對象
    if (iterable[Symbol.iterator] == undefined) {
        let err = new TypeError(typeof iterable + iterable + " is not iterable (cannot read property Symbol(Symbol.iterator))");
        return Promise.reject(err);
    }

    //如果 iterable 對象為空
    if (iterable.length === 0) {
        return Promise.resolve([]);
    }

    //其它情況用異步處理
    var pro = new Promise(),    //all 返回的 promise 對象
        valueArr = [];         //all 返回的 promise 對象的 value 屬性
    setTimeout(function() {
        var index = 0,     //記錄當前索引
        count = 0,
        len = iterable.length;

        for (let val of iterable) { -
            function(i) {
                if (val instanceof Promise) { //當前值為 Promise 對象時
                    if (val.status === "pending") {
                        val.then(function(value) {
                            valueArr[i] = value;
                            count++;
                            //Promise.all([new Promise(function(resolve){setTimeout(resolve, 100, 1)}), 2, 3, 4])
                            if (count === len) {
                                pro.value = valueArr;
                                pro.status = "fulfilled";
                            }
                        },
                        function(reason) {
                            pro.reason = reason;
                            pro.status = "rejected";
                            //當一個pending Promise首先完成時,解除其它 pending Promise的事件,防止之后其它 Promise 改變 pro 的狀態
                            for (let uselessPromise of iterable) {
                                if (uselessPromise instanceof Promise && uselessPromise.status === "pending") {
                                    uselessPromise.events.removeEvent("change");
                                }
                            }
                        });
                    } else if (val.status === "rejected") {
                        pro.reason = val.reason;
                        pro.status = "rejected";
                        return;
                    } else {
                        //val.status === "fulfilled"
                        valueArr[i] = val.value;
                        count++;
                    }
                } else {
                    valueArr[i] = val;
                    count++;
                }
                index++;
            } (index);
        }

        //如果 iterable 對象中的 promise 對象都變為 fulfilled 狀態,或者 iterable 對象內沒有 promise 對象,
        //由于我們可能需要等待 pending promise 的結果,所以要額外花費一個變量計數,而不能用valueArr的長度判斷。
        if (count === len) {
            pro.value = valueArr;
            pro.status = "fulfilled";
        }
    },
    0);

    return pro;
}

這里解釋兩點:

1、如何保證 value 數組中值的順序
如果iterable對象中的 promise 對象都變為 fulfilled 狀態,或者 iterable 對象內沒有 promise 對象,all 返回一個 fulfilled promise 對象,且其 value 值為 iterable 中各項值組成的數組,數組中的值將會按照 iterable 內的順序排列,而不是由 pending promise 的完成順序決定。
為了保證 value 數組中值的順序,最簡單的方法是

valueArr[iterable.indexOf(val)] = val.value;

但是像除 Array、TypedArray 和 String 外的 Map 和 Set 原生 iterabe 對象,以及其它通過myIterable[Symbol.iterator] 創建的自定義的 iterable 對象都沒有 indexOf 方法,所以我選擇用閉包來保證 value 數組值的順序。

2、處理 pending promise 對象。
pending promise 是導致這個函數要額外添加很多變量存儲狀態,額外做很多判斷和處理的罪魁禍首。
如果 iterabe 對象中有一個pending狀態的 promise(通常為一個異步的 promise),我們就使用then方法來持續關注它的動態。

一旦它變成fulfilledpromise,就將它的 value 加入 valueArr 數組。我們添加一個 count 變量記錄目前 valueArr 獲取到了多少個值,當全部獲取到值后,就可以給 pro.value 和pro.status 賦值了。之所以用 count 而不是 valueArr.length 判斷,是因為 valueArr = [undefined,undefined,undefined,1] 的長度也為4,這樣可能導致還沒獲取到 pending promise 的值就改變 pro.status 了。

而當它變成rejectedpromise 時,我們就更新 all 方法返回的對象的 reason 值,同時改變狀態 status 為 rejected,觸發綁定的onrejected函數。另外,為了與原生 Promise 表現相同:如果 iterable 對象中任意一個 pending promise 對象狀態變為 rejected,將不再持續關注其它 pending promise 的動態。而我早就在所有的 pending promise 上都綁定了 onfulfilled 和 onrejected 函數,用來跟蹤它們。所以我需要在某個 pending promise 變為 rejected promise 時,刪除它們綁定的回調函數。

實例方法then

Promise.prototype.then(onFulfilled, onRejected):

Promise.prototype.then = function(onFulfilled, onRejected) {
    var pro = new Promise();

    //綁定回調函數,onFulfilled 和 onRejected 用一個回調函數處理
    this.events.addEvent("change", hander.bind(null, this));

    function hander(that) {
        var res; //onFulfilled 或 onRejected 回調函數執行后得到的結果
        try {
            if (that.status === "fulfilled") {
                //如果onFulfilled不是函數,它會在then方法內部被替換成一個 Identity 函數
                typeof onFulfilled !== "function" ? onFulfilled = identity: false;
                //將參數 this.value 傳入 onFulfilled 并執行,將結果賦給 res
                res = onFulfilled.call(null, that.value);
            } else if (that.status === "rejected") {
                //如果onRejected不是函數,它會在then方法內部被替換成一個 Thrower 函數
                typeof onRejected !== "function" ? onRejected = thrower: false;

                res = onRejected.call(null, that.reason);
            }
        } catch(err) {
            //拋出一個錯誤,情況3
            pro.reason = err;
            pro.status = "rejected";

            return;
        }

        if (res instanceof Promise) {
            if (res.status === "fulfilled") {            //情況4
                pro.value = res.value;
                pro.status = "fulfilled";
            } else if (res.status === "rejected") {      //情況5
                pro.reason = res.reason;
                pro.status = "rejected";
            } else {                                     //情況6
                //res.status === "pending"時,pro 跟隨 res
                pro.status = "pending";
                res.then(function(value) {
                    pro.value = value;
                    pro.status = "fulfilled";
                },
                function(reason) {
                    pro.reason = reason;
                    pro.status = "rejected";
                });
            }
        } else {
            //回調函數返回一個值或不返回任何內容,情況1、2
            pro.value = res;
            pro.status = "fulfilled";
        }
    }

    return pro;
};

我想我已經注釋得很清楚了,可以對照我上一篇文章進行閱讀。
我再說明一下pending promise 的“跟隨”情況,和 all 方法的實現方式差不多,這里也是用 res.then來“跟隨”的。我相信大家都看得懂代碼,下面我舉個例子來實踐一下:

var fromCallback;

var fromThen = Promise.resolve("done")
.then(function onFulfilled(value) {
    fromCallback = new Promise(function(resolve){
        setTimeout(() => resolve(value), 0);    //未執行 setTimeout 的回調方法之前 fromCallback 為"pending"狀態
    });
    return fromCallback;    //then 方法返回的 fromThen 將跟隨 onFulfilled 方法返回的 fromCallback
});

setTimeout(function() {
    //目前已執行完 onFulfilled 回調函數,fromCallback 為"pending"狀態,fromThen ‘跟隨’ fromCallback
    console.log(fromCallback.status);    //fromCallback.status === "pending"
    console.log(fromThen.status);        //fromThen.status === "pending"
    setTimeout(function() {
        //目前已執行完 setTimeout 中的回調函數,fromCallback 為"fulfilled"狀態,fromThen 也跟著變為"fulfilled"狀態
        console.log(fromCallback.status + " " + fromCallback.value);    //fromCallback.status === "fulfilled"
        console.log(fromThen.status + " " + fromThen.value);        //fromThen.status === "fulfilled"
        console.log(fromCallback === fromThen);        //false
    }, 10);    //將這個 delay 參數改為 0 試試
}, 0);

看完這個例子,我相信大家都搞懂了then的回調函數返回 pending promise 時它會怎么處理了。
另外,這個例子也體現出我用 setTimeout 分發的macrotask模擬microtask的不足之處了,如果將倒數第二行的的 delay 參數改為 0,那么 fromThen.status === "pending",這說明修改它狀態的代碼在 log 它狀態的代碼之后執行,至于原因大家自己想一下,這涉及到 event loop。

測試

各位大俠請點下面的鏈接進行測試:
https://codepen.io/lyl123321/...

或者直接點這里查看源代碼:
https://github.com/lyl123321/...
新增 Promise.try:
https://github.com/lyl123321/...

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/102611.html

相關文章

  • Promise——閱讀文檔簡單實現(一)

    摘要:意味著操作成功完成。方法接收失敗情況的回調函數作為參數,返回一個對象。參數回調函數不接收任何參數,當對象變成狀態時被調用。現在各個方法的參數返回值功能和使用方法已經有個大概的了解了,為了進一步理解其原理,接下來我打算簡單地實現一下它。 前言 最近幾周參加筆試面試,總是會遇到實現異步和處理異步的問題,然而作者每次都無法完美地回答。在最近一次筆試因為 Promise 而被刷掉后,我終于下定...

    yanwei 評論0 收藏0
  • JavaScript 異步

    摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。寫一個符合規范并可配合使用的寫一個符合規范并可配合使用的理解的工作原理采用回調函數來處理異步編程。 JavaScript怎么使用循環代替(異步)遞歸 問題描述 在開發過程中,遇到一個需求:在系統初始化時通過http獲取一個第三方服務器端的列表,第三方服務器提供了一個接口,可通過...

    tuniutech 評論0 收藏0
  • Node程序debug小記

    摘要:當前的部分代碼狀態超時再縮小了范圍以后,進一步進行排查。函數是一個很簡單的一次性函數,在第一次被觸發時調用函數。因為上述使用的是,而非,所以在獲取的時候,肯定為空,那么這就意味著會繼續調用函數。 有時候,所見并不是所得,有些包,你需要去翻他的源碼才知道為什么會這樣。 背景 今天調試一個程序,用到了一個很久之前的NPM包,名為formstream,用來將form表單數據轉換為流的形式進行...

    Achilles 評論0 收藏0
  • 理解 Javascript 中的 Promise

    摘要:理解承諾有兩個部分。如果異步操作成功,則通過的創建者調用函數返回預期結果,同樣,如果出現意外錯誤,則通過調用函數傳遞錯誤具體信息。這將與理解對象密切相關。這個函數將創建一個,該將在到秒之間的隨機數秒后執行或。 想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等著你! showImg(https://segmentfault.com/img/bVbkNvF?w=1280&h=...

    paulli3 評論0 收藏0
  • 理解 Javascript 中的 Promise

    摘要:理解承諾有兩個部分。如果異步操作成功,則通過的創建者調用函數返回預期結果,同樣,如果出現意外錯誤,則通過調用函數傳遞錯誤具體信息。這將與理解對象密切相關。這個函數將創建一個,該將在到秒之間的隨機數秒后執行或。 想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等著你! showImg(https://segmentfault.com/img/bVbkNvF?w=1280&h=...

    chaos_G 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<