成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Promise——從閱讀文檔到簡單實(shí)現(xiàn)(二)

dinfer / 3609人閱讀

摘要:在和方法執(zhí)行的時(shí)候訂閱事件,將自己的回調(diào)函數(shù)綁定到事件上,屬性是發(fā)布者,一旦它的值發(fā)生改變就發(fā)布事件,執(zhí)行回調(diào)函數(shù)。實(shí)現(xiàn)和方法的回調(diào)函數(shù)都是,當(dāng)滿足條件對象狀態(tài)改變時(shí),這些回調(diào)會被放入隊(duì)列。所以我需要在某個(gè)變?yōu)闀r(shí),刪除它們綁定的回調(diào)函數(shù)。

前言

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

數(shù)據(jù)傳遞

回調(diào)綁定

將回調(diào)變成 microtask

實(shí)現(xiàn) then/finally 返回的pending promise “跟隨”它們的回調(diào)返回的pending promise

實(shí)現(xiàn) resolve 返回的 promise “跟隨”它的thenable對象參數(shù)

實(shí)現(xiàn)框架

在解決上述問題前,我們先實(shí)現(xiàn)一個(gè)框架。
首先,我的目的是實(shí)現(xiàn)一個(gè)Promise插件,它包括:

構(gòu)造函數(shù):Promise

靜態(tài)方法:resolve、reject、all 和 race

實(shí)例方法:then、catch 和 finally

私有函數(shù):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;
})();
解決問題

接下來,我們解決各個(gè)問題。

數(shù)據(jù)傳遞

為了傳遞數(shù)據(jù)——回調(diào)函數(shù)需要用到的參數(shù)以及 promise 的狀態(tài),我們首先在構(gòu)造函數(shù)Promise中給新生成的對象添加status、valuereason屬性,并且在構(gòu)造函數(shù)中執(zhí)行 executor 函數(shù):

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 對象的引用)來訪問這些數(shù)據(jù),并將其用作回調(diào)函數(shù)的參數(shù)。

按照文檔說明,為了實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用,Promise的所有方法都會返回一個(gè) Promise 對象,而且除了Promise.resolve(peomiseObj) 這種情況外都是新生成的 Promise 對象。所以接下來我的大部分方法都會返回一個(gè)新的 promise 對象。不生成新對象的特例:

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

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

function Promise(executor) {
    var self = this;

    //存儲狀態(tài)的私有屬性
    this._status = "pending";

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

    //存儲狀態(tài)的公開屬性
    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;
}

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

實(shí)現(xiàn) microtask

then、catchfinally方法的回調(diào)函數(shù)都是microtask,當(dāng)滿足條件(promise 對象狀態(tài)改變)時(shí),這些回調(diào)會被放入microtask隊(duì)列。每當(dāng)調(diào)用棧中的macrotask執(zhí)行完畢時(shí),立刻執(zhí)行microtask隊(duì)列中所有的microtask,這樣一次事件循環(huán)就結(jié)束了,js引擎等待下一次循環(huán)。
要我實(shí)現(xiàn)microtask我是做不到的,我就只能用macrotask模仿一下microtask了。
我用 setTimeout 發(fā)布的macrotask進(jìn)行模仿:

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

接下來,我們實(shí)現(xiàn)各個(gè)函數(shù)和方法。在知道方法的參數(shù)和返回值后再實(shí)現(xiàn)方法如有神助,而實(shí)現(xiàn)過程中最難處理的就是 pending 狀態(tài)的 promise 對象,因?yàn)槲覀円人兂善渌鼱顟B(tài)時(shí),再做真正的處理。下面我拿出兩個(gè)最具代表性的方法來分析。

靜態(tài)方法all

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

function all(iterable) {
    //如果 iterable 不是一個(gè)可迭代對象
    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,     //記錄當(dāng)前索引
        count = 0,
        len = iterable.length;

        for (let val of iterable) { -
            function(i) {
                if (val instanceof Promise) { //當(dāng)前值為 Promise 對象時(shí)
                    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";
                            //當(dāng)一個(gè)pending Promise首先完成時(shí),解除其它 pending Promise的事件,防止之后其它 Promise 改變 pro 的狀態(tài)
                            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 對象都變?yōu)?fulfilled 狀態(tài),或者 iterable 對象內(nèi)沒有 promise 對象,
        //由于我們可能需要等待 pending promise 的結(jié)果,所以要額外花費(fèi)一個(gè)變量計(jì)數(shù),而不能用valueArr的長度判斷。
        if (count === len) {
            pro.value = valueArr;
            pro.status = "fulfilled";
        }
    },
    0);

    return pro;
}

這里解釋兩點(diǎn):

1、如何保證 value 數(shù)組中值的順序
如果iterable對象中的 promise 對象都變?yōu)?fulfilled 狀態(tài),或者 iterable 對象內(nèi)沒有 promise 對象,all 返回一個(gè) fulfilled promise 對象,且其 value 值為 iterable 中各項(xiàng)值組成的數(shù)組,數(shù)組中的值將會按照 iterable 內(nèi)的順序排列,而不是由 pending promise 的完成順序決定。
為了保證 value 數(shù)組中值的順序,最簡單的方法是

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

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

2、處理 pending promise 對象。
pending promise 是導(dǎo)致這個(gè)函數(shù)要額外添加很多變量存儲狀態(tài),額外做很多判斷和處理的罪魁禍?zhǔn)住?br>如果 iterabe 對象中有一個(gè)pending狀態(tài)的 promise(通常為一個(gè)異步的 promise),我們就使用then方法來持續(xù)關(guān)注它的動(dòng)態(tài)。

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

而當(dāng)它變成rejectedpromise 時(shí),我們就更新 all 方法返回的對象的 reason 值,同時(shí)改變狀態(tài) status 為 rejected,觸發(fā)綁定的onrejected函數(shù)。另外,為了與原生 Promise 表現(xiàn)相同:如果 iterable 對象中任意一個(gè) pending promise 對象狀態(tài)變?yōu)?rejected,將不再持續(xù)關(guān)注其它 pending promise 的動(dòng)態(tài)。而我早就在所有的 pending promise 上都綁定了 onfulfilled 和 onrejected 函數(shù),用來跟蹤它們。所以我需要在某個(gè) pending promise 變?yōu)?rejected promise 時(shí),刪除它們綁定的回調(diào)函數(shù)。

實(shí)例方法then

Promise.prototype.then(onFulfilled, onRejected):

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

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

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

                res = onRejected.call(null, that.reason);
            }
        } catch(err) {
            //拋出一個(gè)錯(cuò)誤,情況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"時(shí),pro 跟隨 res
                pro.status = "pending";
                res.then(function(value) {
                    pro.value = value;
                    pro.status = "fulfilled";
                },
                function(reason) {
                    pro.reason = reason;
                    pro.status = "rejected";
                });
            }
        } else {
            //回調(diào)函數(shù)返回一個(gè)值或不返回任何內(nèi)容,情況1、2
            pro.value = res;
            pro.status = "fulfilled";
        }
    }

    return pro;
};

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

var fromCallback;

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

setTimeout(function() {
    //目前已執(zhí)行完 onFulfilled 回調(diào)函數(shù),fromCallback 為"pending"狀態(tài),fromThen ‘跟隨’ fromCallback
    console.log(fromCallback.status);    //fromCallback.status === "pending"
    console.log(fromThen.status);        //fromThen.status === "pending"
    setTimeout(function() {
        //目前已執(zhí)行完 setTimeout 中的回調(diào)函數(shù),fromCallback 為"fulfilled"狀態(tài),fromThen 也跟著變?yōu)?fulfilled"狀態(tài)
        console.log(fromCallback.status + " " + fromCallback.value);    //fromCallback.status === "fulfilled"
        console.log(fromThen.status + " " + fromThen.value);        //fromThen.status === "fulfilled"
        console.log(fromCallback === fromThen);        //false
    }, 10);    //將這個(gè) delay 參數(shù)改為 0 試試
}, 0);

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

測試

各位大俠請點(diǎn)下面的鏈接進(jìn)行測試:
https://codepen.io/lyl123321/...

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

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/102611.html

相關(guān)文章

  • Promise——閱讀文檔簡單實(shí)現(xiàn)(一)

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

    yanwei 評論0 收藏0
  • JavaScript 異步

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

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

    摘要:當(dāng)前的部分代碼狀態(tài)超時(shí)再縮小了范圍以后,進(jìn)一步進(jìn)行排查。函數(shù)是一個(gè)很簡單的一次性函數(shù),在第一次被觸發(fā)時(shí)調(diào)用函數(shù)。因?yàn)樯鲜鍪褂玫氖?,而非,所以在獲取的時(shí)候,肯定為空,那么這就意味著會繼續(xù)調(diào)用函數(shù)。 有時(shí)候,所見并不是所得,有些包,你需要去翻他的源碼才知道為什么會這樣。 背景 今天調(diào)試一個(gè)程序,用到了一個(gè)很久之前的NPM包,名為formstream,用來將form表單數(shù)據(jù)轉(zhuǎn)換為流的形式進(jìn)行...

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

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

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

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

    chaos_G 評論0 收藏0

發(fā)表評論

0條評論

最新活動(dòng)
閱讀需要支付1元查看
<