溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

理解異步之美:Promise 與 async await(二)

發(fā)布時(shí)間:2020-07-08 08:32:43 來(lái)源:網(wǎng)絡(luò) 閱讀:1882 作者:MDove 欄目:web開(kāi)發(fā)

承上啟下

理解異步之美:Promise與async await(一)

經(jīng)歷了上一篇基礎(chǔ)的Promise講解后我覺(jué)得大家對(duì)于promise的基本用法和想法就有一定了解了。(就是一種承諾喲)

下面我們要去了解一下它的工作流程。

結(jié)合源碼與分析別人的常見(jiàn)實(shí)現(xiàn)進(jìn)行理解

下面是別人實(shí)現(xiàn)的總源碼,(簡(jiǎn)單一看就可以)

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;
function Promise(callback) {
    this.status = PENDING;
    this.value = null;
    this.defferd = [];
    setTimeout(callback.bind(this, this.resolve.bind(this), this.reject.bind(this)), 0);
}
    Promise.prototype = {
        constructor: Promise,
        resolve: function (result) {
            this.status = FULFILLED;
            this.value = result;
            this.done();
        },
        reject: function (error) {
            this.status = REJECTED;
            this.value = error;
        },
        handle: function (fn) {
            if (!fn) {
                return;
            }
            var value = this.value;
            var t = this.status;
            var p;
            if (t == PENDING) {
                this.defferd.push(fn);
            } else {
                if (t == FULFILLED && typeof fn.onfulfiled == 'function') {
                    p = fn.onfulfiled(value);
                }
                if (t == REJECTED && typeof fn.onrejected == 'function') {
                    p = fn.onrejected(value);
                }
                var promise = fn.promise;
                if (promise) {
                    if (p && p.constructor == Promise) {
                        p.defferd = promise.defferd;
                    } else {
                        p = this;
                        p.defferd = promise.defferd;
                        this.done();
                    }
                }
            }
        },
        done: function () {
            var status = this.status;
            if (status == PENDING) {
                return;
            }
            var defferd = this.defferd;
            for (var i = 0; i < defferd.length; i++) {
                this.handle(defferd[i]);
            }
        },
        then: function (success, fail) {
            var o = {
                onfulfiled: success,
                onrejected: fail
            };
        var status = this.status;
        o.promise = new this.constructor(function () {});
        if (status == PENDING) {
            this.defferd.push(o);
        } else if (status == FULFILLED || status == REJECTED) {
            this.handle(o);
        }
        return o.promise;
        }
    };

這是網(wǎng)上一份常見(jiàn)的Promise的源碼實(shí)現(xiàn)我會(huì)對(duì)這個(gè)進(jìn)行一個(gè)分析
(肯定有人問(wèn)為什么不自己實(shí)現(xiàn)一個(gè)? 解:省時(shí)、網(wǎng)上太多了、本質(zhì)還是要了解思想)

話(huà)不多說(shuō)開(kāi)始咯

咱們先大體梳理一下實(shí)現(xiàn)的東西要能干什么?

first :

let promsie = new Promise((resolve,reject)=>{
    doSomething()
})
根據(jù)這個(gè)構(gòu)造函數(shù),我們需要實(shí)現(xiàn)兩個(gè)方法,resolve、reject方法。

second :

promise.then(res=>{
    doSomethingRes();
},rej=>{
    doSomenthingRej()
})
我們要實(shí)現(xiàn)一個(gè)then方法,可以去根據(jù)不同狀態(tài)來(lái)執(zhí)行不同的函數(shù)。

最基本的兩個(gè)內(nèi)容我們已經(jīng)確定了。

話(huà)不多說(shuō)開(kāi)始分析代碼。

對(duì)代碼的分析在內(nèi)容的注釋上大家不要遺漏哈?。?!

第一段構(gòu)造函數(shù)與狀態(tài)設(shè)定

// 首先聲明三個(gè)狀態(tài),
// 狀態(tài)的就是上一節(jié)說(shuō)的:事情是進(jìn)行中、已完成、失敗了。
// 這三種狀態(tài)遵循著PENDING->FULEFILLED 或者PENDING->REJECTED
var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;
// Promise構(gòu)造函數(shù)接收u一個(gè)回調(diào)函數(shù)
function Promise(callback) {
    // 新new出來(lái)的實(shí)例的status一定是PENDING.
    this.status = PENDING;
    // value是指當(dāng)你事情完成、失敗后內(nèi)部保存的值
    // 用法是resolve(42) 在then函數(shù)的res=>{dosomething()res就是42
    }
    this.value = null;
    // defferd 字面意思推遲、是個(gè)數(shù)組,存放這個(gè)promise以后要執(zhí)行的事件
    // 類(lèi)比發(fā)布訂閱模式(觀(guān)察者模式)。存放觀(guān)察者在被觀(guān)察者身上訂閱的事件列表
    // defferd內(nèi)的事件存放著日后觀(guān)察者要執(zhí)行的事件。
    this.defferd = [];
    // setTimeout異步的去執(zhí)行new 一個(gè)promise實(shí)例內(nèi)執(zhí)行的任務(wù),不去阻塞主線(xiàn)程。
    //這一段代碼我有一點(diǎn)疑惑,new promise實(shí)例時(shí),callback的執(zhí)行并不是異步的。
    // 而這里選擇異步的并不是很合理。
    // bind函數(shù)的作用。callback的參數(shù)如何指定?通過(guò)bind方法將函數(shù)函數(shù)柯里化(Currying 和一個(gè)NBA球星的名字一樣很好記)
    // 而且綁定函數(shù)的this執(zhí)行。函數(shù)內(nèi)的this都執(zhí)行這個(gè)new 出來(lái)的promise實(shí)例
    // 去指定函數(shù)執(zhí)行時(shí)的參數(shù),將resolve方法與reject方法強(qiáng)制做為callback的參數(shù)。
    // 所以我們寫(xiě)的回調(diào)函數(shù),參數(shù)怎么命名都可以執(zhí)行到對(duì)應(yīng)的resolve與reject方法
    setTimeout(callback.bind(this, this.resolve.bind(this), this.reject.bind(this)), 0);
}

自己實(shí)現(xiàn)與官方Promise執(zhí)行的對(duì)比。大家可以看一下這個(gè)setTimeout導(dǎo)致的執(zhí)行順序問(wèn)題。所以閱讀別人對(duì)各種功能實(shí)現(xiàn)時(shí)要學(xué)會(huì)對(duì)照的去看。
理解異步之美:Promise 與 async await(二)cdn.xitu.io/2018/8/18/1654c206c85e9099?w=1124&h=440&f=png&s=88739">

理解異步之美:Promise 與 async await(二)

到這里伙伴們已經(jīng)了解了我們new 一個(gè)構(gòu)造函數(shù)時(shí)都會(huì)做哪些事情。

1:對(duì)promise實(shí)例定義一個(gè)狀態(tài),值為PENDING。

2:給promise實(shí)例定義一個(gè)存放值的空間。

3:設(shè)置一個(gè)發(fā)布列表,在以后的指定時(shí)間發(fā)布其中的事件。

4:通過(guò)bind函數(shù)將callback柯里化,使callback執(zhí)行時(shí)調(diào)用對(duì)應(yīng)的resolve與reject方法,并執(zhí)行callback

第二段 resolve reject then方法的分析

為什么先說(shuō)這三個(gè)方法。
因?yàn)閞esolve、reject是核心方法,不說(shuō)都不行,可是resolve與reject完成要做的事情必須是then方法指定的所以三個(gè)方法之間關(guān)系密切。

// 在Promise的原型對(duì)象上指定這些方法。
// 這種做法有很大弊端、并且Promise源碼也并不是這么做的之后會(huì)進(jìn)行分析
Promise.prototype = {
        // 覆蓋式的指定Promise的原型對(duì)象會(huì)導(dǎo)致constructor屬性丟失
        // 在這里進(jìn)行填補(bǔ),手動(dòng)指定Promise原型對(duì)象上的constructor屬性
        constructor: Promise,
        // resolve方法開(kāi)始 接收一個(gè)結(jié)果(可以為空)
        resolve: function (result) {
            //更改狀態(tài)為FULFILLED。
            this.status = FULFILLED;
            // 將result存放在之前構(gòu)造函數(shù)中提到的存放結(jié)果的空間中
            this.value = result;
            // done方法。表示執(zhí)行完畢(后面會(huì)繼續(xù)將)
            this.done();
        },
        // 與resolve方法類(lèi)似 不多做解釋
        reject: function (error) {
            // 狀態(tài)更改
            this.status = REJECTED;
            this.value = error;
            // 沒(méi)有done函數(shù),這塊做法很有問(wèn)題下面會(huì)配圖解釋。
        },
        // then方法開(kāi)始要好好講講
        // success表示狀態(tài)變成FULFILLED時(shí)要執(zhí)行的函數(shù)
        // fail表示狀態(tài)變成REJECTED時(shí)要執(zhí)行的函數(shù)
        then: function (success, fail) {
        // 聲明一個(gè)對(duì)象來(lái)存放這些事件。
            var o = {
                onfulfiled: success,
                onrejected: fail
            };
        // 獲取當(dāng)前promise的狀態(tài)。
        // 這個(gè)的意義是,我們對(duì)promise實(shí)例執(zhí)行then方法的時(shí)候有兩種情況
        // 一:promise實(shí)例的內(nèi)容還沒(méi)有執(zhí)行完畢。二:promise實(shí)例內(nèi)容已經(jīng)執(zhí)行完畢并且狀態(tài)已經(jīng)改變。繼續(xù)下面。
        var status = this.status;
        o.promise = new this.constructor(function () {});
        // 如果狀態(tài)是PENDING,表示實(shí)例內(nèi)容還未執(zhí)行完畢。
        // 說(shuō)明then方法指定在某種狀態(tài)要執(zhí)行的事件是未來(lái)發(fā)生的現(xiàn)在并不執(zhí)行。
        // 所以將o放入defferd訂閱列表中。
        // 這個(gè)之前講過(guò)defferd中的內(nèi)容會(huì)在某個(gè)條件觸發(fā)后會(huì)執(zhí)行。
        // 所以當(dāng)promise實(shí)例內(nèi)容還未完成就要把未來(lái)執(zhí)行的方法放入訂閱列表
        if (status == PENDING) {
            this.defferd.push(o);
        // 對(duì)應(yīng)之前說(shuō)的情況二    
        // 狀態(tài)變成以下兩種情況怎么辦?
        // 訂閱列表內(nèi)不應(yīng)該有當(dāng)前情況的o。
        // 所以要立即執(zhí)行當(dāng)前指定的事件,而且未來(lái)的任何情況下這次指定的事件也不會(huì)執(zhí)行。
        //所以不會(huì)放入defferd中
        } else if (status == FULFILLED || status == REJECTED) {
           // 執(zhí)行handle函數(shù)
            this.handle(o);
        }
        // then方法存在鏈?zhǔn)秸{(diào)用,then方法的返回值必須是一個(gè)promise對(duì)象
        return o.promise;
        }
    };

到這里我們大體梳理清楚,resolve、reject、then方法的用處。

提一嘴 reject方法沒(méi)有執(zhí)行done函數(shù)會(huì)導(dǎo)致以下情況

理解異步之美:Promise 與 async await(二)

一:在new promise實(shí)例過(guò)程中執(zhí)行的callback函數(shù),在函數(shù)執(zhí)行的過(guò)程中肯定會(huì)調(diào)用resolve或者reject(兩個(gè)都調(diào)用也可能)。當(dāng)調(diào)用了resolve方法之后會(huì)改變promise的狀態(tài),存放結(jié)果。表示任務(wù)完成,執(zhí)行done函數(shù)。(reject就不再來(lái)一遍了)

二:then方法的執(zhí)行事件與resolve方法沒(méi)有任何先后順序可言。隨心所欲誰(shuí)在前面都不一定。在resolve(reject)之前執(zhí)行,就注冊(cè)一下要執(zhí)行的事件。在resolve(reject)之后執(zhí)行就直接執(zhí)行就可以了,并且不要注冊(cè)。

第三段 具體是怎么執(zhí)行的呢?聊聊done與handle

同學(xué)們按照代碼執(zhí)行的順序,我們應(yīng)該先去看done方法。然后再看handle所以辛苦一下,先向下一點(diǎn)找到可愛(ài)的done方法。

// 看完done方法的朋友肯定對(duì)我這種方式很鬧心,保證你們看的熱情嘛。哈哈哈哈哈
// 沒(méi)看done方法的快回去看、快回去看。
// 必須提一下 下面一直說(shuō)的o是什么? o是在then方法中傳入defferd數(shù)組中的對(duì)象,一下簡(jiǎn)稱(chēng)為o。
// handle是干嘛的??? 那是用來(lái)執(zhí)行o的。o里面放著我們想要執(zhí)行的內(nèi)容
// 大家再回憶以下,handle還在哪里執(zhí)行了?想起來(lái)了吧,當(dāng)then方法執(zhí)行時(shí)
// 如果狀態(tài)已經(jīng)改變了。那么就直接handle(o),執(zhí)行你要做的事情。
handle: function (fn) {
    // o不存在的???媽耶,咋辦呀。那就是defferd中沒(méi)東西。好吧什么都不做
    if (!fn) {
        return;
    }
    var value = this.value;
    var t = this.status;
    var p;
    // 如果狀態(tài)為PENDING,表示還沒(méi)到o要執(zhí)行的內(nèi)容。那么不能執(zhí)行的?
    if (t == PENDING) {

        this.defferd.push(fn);
    } else {
        // 這里面很容易看的,狀態(tài)變成FULFILLED,
        //并且你在給狀態(tài)是FULFILLED時(shí)要做的事情可以執(zhí)行(函數(shù)才能執(zhí)行呀,你寫(xiě)個(gè)字符串不報(bào)錯(cuò)了??)
        // 執(zhí)行咯。這里面大家一看就知道,你then方法里面res=>{doSometthing(res)}
        //這個(gè)res就是promise內(nèi)存放的結(jié)果(value)。
        if (t == FULFILLED && typeof fn.onfulfiled == 'function') {
            p = fn.onfulfiled(value);
        }
        // 不多提了。
        if (t == REJECTED && typeof fn.onrejected == 'function') {
            p = fn.onrejected(value);
        }
        // 但是這個(gè)p是干什么的????
        // 存放方法的返回值,為了鏈?zhǔn)秸{(diào)用。實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用的是什么方法?
        // 返回的o.promise. 那么返回的o.promise不存在呢?(當(dāng)然這是不可能的)
        // 那就沒(méi)有鏈?zhǔn)秸{(diào)用。
        // promise中有一個(gè)方法,當(dāng)then函數(shù)內(nèi)的事件(指的這個(gè)函數(shù):res=>{})
        // 返回值是一個(gè)promise對(duì)象時(shí),那么then的返回值就是這個(gè)promise對(duì)象
        // 鏈?zhǔn)街械南乱粋€(gè)then就會(huì)等待這個(gè)promise執(zhí)行完畢。
        // 如果不是promise對(duì)象怎么辦?那么就執(zhí)行鏈條后面的then方法注冊(cè)的事情。
        var promise = fn.promise;
        if (promise) {
            // 如果你注冊(cè)的事件執(zhí)行后的返回值是一個(gè)promise對(duì)象
            if (p && p.constructor == Promise) {
                // 當(dāng)前這個(gè)p(是個(gè)promise對(duì)象)可以把o.promise對(duì)象訂閱列表內(nèi)的事件拿過(guò)來(lái)。
                // 串行后,返回的promise繼續(xù)控制著defferd的內(nèi)容。
                //理論上講,按剛才的邏輯來(lái)寫(xiě),每個(gè)promise對(duì)象內(nèi)的defferd,都應(yīng)該只有一個(gè)值。
                // 因?yàn)榇械逆湕l每個(gè)then注冊(cè)的事件都在上一個(gè)then返回的o.promise的defferd內(nèi)。
                // 那么為什么?defferd要寫(xiě)個(gè)數(shù)組呢???這是我疑惑的地方但是影響不大
                p.defferd = promise.defferd;
            } else {
               // 如果不是promise呢?那么就把當(dāng)前的promise對(duì)象當(dāng)作你的返回值
               // 繼續(xù)繼承o.promise里面的defferd
                p = this;
                p.defferd = promise.defferd;
                // 并沒(méi)有任何承諾,p應(yīng)該就是一個(gè)resolved(reject)狀態(tài)
                // 直接執(zhí)行done方法就可以啦。
                this.done();
            }
        }
    }
},
// done方法是在resolve方法(reject為啥沒(méi)寫(xiě)之前有講過(guò))中執(zhí)行的,表示resolve方法執(zhí)行完畢了。
// 這個(gè)完畢是一種明確信號(hào),那就是之前說(shuō)好的狀態(tài)變成FULFILLED我要做的那些事情。
// 鄉(xiāng)親們、弟兄們?cè)蹅兛梢员粓?zhí)行了
done: function () {
    // 當(dāng)然為了保險(xiǎn)起見(jiàn)PENDING狀態(tài)肯定不會(huì)執(zhí)行,return掉。
    // done函數(shù)在PENDING狀態(tài)也不會(huì)被調(diào)用呀。雙重保險(xiǎn)嘛
    var status = this.status;
    if (status == PENDING) {
        return;
    }
    // 鄉(xiāng)親們、兄弟們?cè)谀哪??就在你的defferd中。我們的訂閱列表內(nèi)的兄弟們?cè)搱?zhí)行了。
    // 遍歷以下我們的defferd對(duì)象,誰(shuí)也別漏下都給我執(zhí)行了。
    var defferd = this.defferd;
    for (var i = 0; i < defferd.length; i++) {
    // 問(wèn)題來(lái)了鄉(xiāng)親們不是函數(shù)是一個(gè)對(duì)象啊。
    //為啥是對(duì)象?在then方法中有defferd.push(o);o是啥?是個(gè)對(duì)象啊。
    // 那咋執(zhí)行????需要一個(gè)特定來(lái)執(zhí)行o的方法,就是handle。
    // 好了伙伴們可以把文章回到上面一點(diǎn)了。
        this.handle(defferd[i]);
    }
},

這一段我已經(jīng)把handle與done方法說(shuō)完了。主要是為了鏈?zhǔn)秸{(diào)用。才會(huì)設(shè)計(jì)的這樣子。所以鏈?zhǔn)秸{(diào)用還是很搶手的一個(gè)功能。
自己也可以嘗試的去實(shí)現(xiàn)一下符合promise規(guī)范的promise功能。

親! 學(xué)習(xí)完要思考

不知道看到這里大家對(duì)網(wǎng)上常見(jiàn)的promise源碼實(shí)現(xiàn)有一種什么樣的感覺(jué)???

我先說(shuō)說(shuō)我的感覺(jué)

看過(guò)源碼(抱歉我的智商是在有限,短時(shí)間內(nèi)是真的看不懂?。X(jué)得源碼做的要合理太多了。看不懂我都覺(jué)得合理。。。。不是對(duì)強(qiáng)者的過(guò)分崇拜,而是真的很合理。網(wǎng)上常見(jiàn)的實(shí)現(xiàn),只是單單的實(shí)現(xiàn)了功能,這樣的promise只適合有一定promise經(jīng)驗(yàn)并且守規(guī)矩的人使用。為什么這么說(shuō)???

一:這樣實(shí)現(xiàn)的promise,狀態(tài)可以隨時(shí)人為的更改,對(duì)外暴露,沒(méi)有設(shè)置為私有屬性。

二:為了方便,選擇把方法設(shè)置在原型鏈上,導(dǎo)致無(wú)法使用私用變量。

三:reject的執(zhí)行不足,只是對(duì)resolve進(jìn)行合理的使用。

雖然我這么說(shuō),我也實(shí)現(xiàn)不出來(lái),寫(xiě)出這些的人還是比我厲害很多

promise的源碼則是(某個(gè)版本的,版本號(hào)我不記得了)

把resolve、reject、all、race,handle方法,都放在構(gòu)造函數(shù)內(nèi)。

把catch、then、chain方法放在原型上。

理解異步之美:Promise 與 async await(二)
有圖為證,字面意思應(yīng)該是這個(gè)意思,我覺(jué)得我沒(méi)想錯(cuò)。
在改變promise的狀態(tài)也好、value也好。都在頻繁的使用PromiseSet方法來(lái)設(shè)置屬性,對(duì)方法進(jìn)行封裝,并且方便狀態(tài)的管理,附加合理的容錯(cuò)。

理解異步之美:Promise 與 async await(二)

對(duì)比源碼之后,覺(jué)得自己雖然流程大體了解,但是這種精密而且優(yōu)雅的方式,是短時(shí)間內(nèi)很難去掌握的。promise的源碼當(dāng)然會(huì)堅(jiān)持看下去,網(wǎng)上能把promise按照規(guī)范實(shí)現(xiàn)一遍的人已經(jīng)很厲害了。我雖然覺(jué)得還有地方可以修改,但是我比他們還差的遠(yuǎn)(這種感覺(jué)就有點(diǎn)像:我不上,我就比比),要向他們學(xué)習(xí),照這他們?nèi)ヅΑ?/p>

別說(shuō)了 喝雞湯吧

前端的學(xué)習(xí)之路還很漫長(zhǎng),我看過(guò)的(僅僅是看過(guò)的)源碼半只手就都數(shù)的過(guò)來(lái)。還是堅(jiān)信堅(jiān)持下去,自己就變得很棒。每個(gè)人都是從控制流語(yǔ)句學(xué)過(guò)來(lái)的,邏輯也不過(guò)是復(fù)雜的控制流程(還涉及高端的算法與設(shè)計(jì)模式),堅(jiān)信自己一定可以成功?。?!一起努力吧 每一個(gè)前端er(boy and girl)。所以一切源碼層面看不懂、不理解都可以歸結(jié)為看得少、想得少、理解的少。(和你的智商沒(méi)有任何關(guān)系喲)

下期預(yù)告

下一篇就是理解異步之美的終點(diǎn)篇了。異步的美好在于這種神奇的思想。抓住思想的尾巴,不被技術(shù)束縛,嘿嘿嘿。

要開(kāi)新課題了。課題應(yīng)該是圍繞著vue-router的源碼進(jìn)行學(xué)習(xí)。一個(gè)與大家分享學(xué)習(xí)過(guò)程的周期性文章。盡情期待?。?!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI