js異步

弄懂js異步

講異步之前,我們必須掌握一個(gè)基礎(chǔ)知識(shí)-event-loop。

我們知道JavaScript的一大特點(diǎn)就是單線程,而這個(gè)線程中擁有唯一的一個(gè)事件循環(huán)。當(dāng)然新標(biāo)準(zhǔn)中的web worker涉及到了多線程,但它的原理是利用一個(gè)父線程和多個(gè)子線程,歸根結(jié)底來(lái)說(shuō)js仍逃不過(guò)是單線程的事實(shí)。

JavaScript代碼的執(zhí)行過(guò)程中,除了依靠函數(shù)調(diào)用棧來(lái)搞定函數(shù)的執(zhí)行順序外,還依靠任務(wù)隊(duì)列(task queue)(先進(jìn)先出)來(lái)搞定另外一些代碼的執(zhí)行

  • 一個(gè)線程中,事件循環(huán)是唯一的,但是任務(wù)隊(duì)列可以擁有多個(gè)。

  • 任務(wù)隊(duì)列又分為macro-task(宏任務(wù))與micro-task(微任務(wù)),在最新標(biāo)準(zhǔn)中,它們被分別稱為task與jobs。

  • macro-task大概包括:script(整體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering。

  • micro-task大概包括: process.nextTick, Promise, Object.observe(已廢棄), MutationObserver(html5新特性)

  • setTimeout/Promise等我們稱之為任務(wù)源。而進(jìn)入任務(wù)隊(duì)列的是他們指定的具體執(zhí)行任務(wù)。

//setTimeout本身是一個(gè)任務(wù)分發(fā)器,他作為一個(gè)任務(wù)源,會(huì)立即執(zhí)行
setTimeout(function() { 
    //里面的函數(shù)是具體任務(wù),它會(huì)延遲執(zhí)行
    console.log('dc');
})

來(lái)自不同任務(wù)源的任務(wù)會(huì)進(jìn)入到不同的任務(wù)隊(duì)列。其中setTimeout與setInterval是同源的。

事件循環(huán)的順序,決定了JavaScript代碼的執(zhí)行順序。它從script(整體代碼)開(kāi)始第一次循環(huán)。之后全局上下文進(jìn)入函數(shù)調(diào)用棧。直到調(diào)用棧清空(只剩全局),然后執(zhí)行所有的micro-task。當(dāng)所有可執(zhí)行的micro-task執(zhí)行完畢之后。循環(huán)再次從macro-task開(kāi)始,找到其中一個(gè)任務(wù)隊(duì)列執(zhí)行完畢,然后再執(zhí)行所有的micro-task,這樣一直循環(huán)下去。

其中每一個(gè)任務(wù)的執(zhí)行,無(wú)論是macro-task還是micro-task,都是借助函數(shù)調(diào)用棧來(lái)完成。

// 為了方便理解,我以打印出來(lái)的字符作為當(dāng)前的任務(wù)名稱
setTimeout(function() {
    console.log('timeout1');
})

new Promise(function(resolve) {
    console.log('promise1');
    for(var i = 0; i < 1000; i++) {
        i == 99 && resolve();
    }
    console.log('promise2');
}).then(function() {
    console.log('then1');
})

console.log('global1');


//////////////
promise1
promise2
global1
then1
timeout1

首先,事件循環(huán)從宏任務(wù)隊(duì)列開(kāi)始,這個(gè)時(shí)候,宏任務(wù)隊(duì)列中,只有一個(gè)script(整體代碼)任務(wù)。每一個(gè)任務(wù)的執(zhí)行順序,都依靠函數(shù)調(diào)用棧來(lái)搞定,而當(dāng)遇到任務(wù)源時(shí),則會(huì)先分發(fā)任務(wù)到對(duì)應(yīng)的隊(duì)列中去,所以,上面例子的第一步執(zhí)行如下圖所示。


首先script任務(wù)開(kāi)始執(zhí)行,全局上下文入棧
首先script任務(wù)開(kāi)始執(zhí)行,全局上下文入棧

第二步:script任務(wù)執(zhí)行時(shí)首先遇到了setTimeout,setTimeout為一個(gè)宏任務(wù)源,那么他的作用就是將任務(wù)分發(fā)到它對(duì)應(yīng)的隊(duì)列中

宏任務(wù)timeout1進(jìn)入setTimeout隊(duì)列
宏任務(wù)timeout1進(jìn)入setTimeout隊(duì)列

第三步:script執(zhí)行時(shí)遇到Promise實(shí)例。Promise構(gòu)造函數(shù)中的第一個(gè)參數(shù),是在new的時(shí)候執(zhí)行,因此不會(huì)進(jìn)入任何其他的隊(duì)列,而是直接在當(dāng)前任務(wù)直接執(zhí)行了,而后續(xù)的.then則會(huì)被分發(fā)到micro-task的Promise隊(duì)列中去。

因此,構(gòu)造函數(shù)執(zhí)行時(shí),里面的參數(shù)進(jìn)入函數(shù)調(diào)用棧執(zhí)行。for循環(huán)不會(huì)進(jìn)入任何隊(duì)列,因此代碼會(huì)依次執(zhí)行,所以這里的promise1和promise2會(huì)依次輸出

promise1入棧執(zhí)行,這時(shí)promise1被最先輸出
promise1入棧執(zhí)行,這時(shí)promise1被最先輸出
resolve在for循環(huán)中入棧執(zhí)行
resolve在for循環(huán)中入棧執(zhí)行
構(gòu)造函數(shù)執(zhí)行完畢的過(guò)程中,resolve執(zhí)行完畢出棧,promise2輸出,promise1也出棧,then執(zhí)行時(shí),Promise任務(wù)then1進(jìn)入對(duì)應(yīng)隊(duì)列
構(gòu)造函數(shù)執(zhí)行完畢的過(guò)程中,resolve執(zhí)行完畢出棧,promise2輸出,promise1也出棧,then執(zhí)行時(shí),Promise任務(wù)then1進(jìn)入對(duì)應(yīng)隊(duì)列

script任務(wù)繼續(xù)往下執(zhí)行,最后只有一句輸出了globa1,然后,全局任務(wù)就執(zhí)行完畢了。

第四步:第一個(gè)宏任務(wù)script執(zhí)行完畢之后,就開(kāi)始執(zhí)行所有的可執(zhí)行的微任務(wù)。這個(gè)時(shí)候,微任務(wù)中,只有Promise隊(duì)列中的一個(gè)任務(wù)then1,因此直接執(zhí)行就行了,執(zhí)行結(jié)果輸出then1,當(dāng)然,他的執(zhí)行,也是進(jìn)入函數(shù)調(diào)用棧中執(zhí)行的。

執(zhí)行所有的微任務(wù)
執(zhí)行所有的微任務(wù)

第五步:當(dāng)所有的micro-tast執(zhí)行完畢之后,表示第一輪的循環(huán)就結(jié)束了。這個(gè)時(shí)候就得開(kāi)始第二輪的循環(huán)。第二輪循環(huán)仍然從宏任務(wù)macro-task開(kāi)始

微任務(wù)被清空

這個(gè)時(shí)候,我們發(fā)現(xiàn)宏任務(wù)中,只有在setTimeout隊(duì)列中還要一個(gè)timeout1的任務(wù)等待執(zhí)行。因此就直接執(zhí)行即可

timeout1入棧執(zhí)行
timeout1入棧執(zhí)行

這個(gè)時(shí)候宏任務(wù)隊(duì)列與微任務(wù)隊(duì)列中都沒(méi)有任務(wù)了,所以代碼就不會(huì)再輸出其他東西了。

那么上面這個(gè)例子的輸出結(jié)果就顯而易見(jiàn)。大家可以自行嘗試體會(huì)。

這個(gè)例子比較簡(jiǎn)答,涉及到的隊(duì)列任務(wù)并不多,因此讀懂了它還不能全面的了解到事件循環(huán)機(jī)制的全貌。所以我下面弄了一個(gè)復(fù)雜一點(diǎn)的例子,再給大家解析一番,相信讀懂之后,事件循環(huán)這個(gè)問(wèn)題,再面試中再次被問(wèn)到就難不倒大家了

console.log('golb1');

setTimeout(function() {
    console.log('timeout1');
    process.nextTick(function() {
        console.log('timeout1_nextTick');
    })
    new Promise(function(resolve) {
        console.log('timeout1_promise');
        resolve();
    }).then(function() {
        console.log('timeout1_then')
    })
})

setImmediate(function() {
    console.log('immediate1');
    process.nextTick(function() {
        console.log('immediate1_nextTick');
    })
    new Promise(function(resolve) {
        console.log('immediate1_promise');
        resolve();
    }).then(function() {
        console.log('immediate1_then')
    })
})

process.nextTick(function() {
    console.log('glob1_nextTick');
})
new Promise(function(resolve) {
    console.log('glob1_promise');
    resolve();
}).then(function() {
    console.log('glob1_then')
})

setTimeout(function() {
    console.log('timeout2');
    process.nextTick(function() {
        console.log('timeout2_nextTick');
    })
    new Promise(function(resolve) {
        console.log('timeout2_promise');
        resolve();
    }).then(function() {
        console.log('timeout2_then')
    })
})

process.nextTick(function() {
    console.log('glob2_nextTick');
})
new Promise(function(resolve) {
    console.log('glob2_promise');
    resolve();
}).then(function() {
    console.log('glob2_then')
})

setImmediate(function() {
    console.log('immediate2');
    process.nextTick(function() {
        console.log('immediate2_nextTick');
    })
    new Promise(function(resolve) {
        console.log('immediate2_promise');
        resolve();
    }).then(function() {
        console.log('immediate2_then')
    })
})

第一步:宏任務(wù)script首先執(zhí)行。全局入棧。glob1輸出。

script首先執(zhí)行
script首先執(zhí)行

第二步,執(zhí)行過(guò)程遇到setTimeout。setTimeout作為任務(wù)分發(fā)器,將任務(wù)分發(fā)到對(duì)應(yīng)的宏任務(wù)隊(duì)列中

timeout1進(jìn)入對(duì)應(yīng)隊(duì)列
timeout1進(jìn)入對(duì)應(yīng)隊(duì)列

第三步:執(zhí)行過(guò)程遇到setImmediate。setImmediate也是一個(gè)宏任務(wù)分發(fā)器,將任務(wù)分發(fā)到對(duì)應(yīng)的任務(wù)隊(duì)列中。setImmediate的任務(wù)隊(duì)列會(huì)在setTimeout隊(duì)列的后面執(zhí)行

setImmediate(function() {
    console.log('immediate1');
    process.nextTick(function() {
        console.log('immediate1_nextTick');
    })
    new Promise(function(resolve) {
        console.log('immediate1_promise');
        resolve();
    }).then(function() {
        console.log('immediate1_then')
    })
})
進(jìn)入setImmediate隊(duì)列
進(jìn)入setImmediate隊(duì)列

第四步:執(zhí)行遇到nextTick,process.nextTick是一個(gè)微任務(wù)分發(fā)器,它會(huì)將任務(wù)分發(fā)到對(duì)應(yīng)的微任務(wù)隊(duì)列中去

process.nextTick(function() {
    console.log('glob1_nextTick');
})
nextTick
nextTick

第五步:執(zhí)行遇到Promise。Promise的then方法會(huì)將任務(wù)分發(fā)到對(duì)應(yīng)的微任務(wù)隊(duì)列中,但是它構(gòu)造函數(shù)中的方法會(huì)直接執(zhí)行。因此,glob1_promise會(huì)第二個(gè)輸出。

new Promise(function(resolve) {
    console.log('glob1_promise');
    resolve();
}).then(function() {
    console.log('glob1_then')
})

先是函數(shù)調(diào)用棧的變化
先是函數(shù)調(diào)用棧的變化
然后glob1_then任務(wù)進(jìn)入隊(duì)列
然后glob1_then任務(wù)進(jìn)入隊(duì)列

第六步:執(zhí)行遇到第二個(gè)setTimeout。

setTimeout(function() {
    console.log('timeout2');
    process.nextTick(function() {
        console.log('timeout2_nextTick');
    })
    new Promise(function(resolve) {
        console.log('timeout2_promise');
        resolve();
    }).then(function() {
        console.log('timeout2_then')
    })
})
timeout2進(jìn)入對(duì)應(yīng)隊(duì)列
timeout2進(jìn)入對(duì)應(yīng)隊(duì)列

第七步:先后遇到nextTick與Promise

process.nextTick(function() {
    console.log('glob2_nextTick');
})
new Promise(function(resolve) {
    console.log('glob2_promise');
    resolve();
}).then(function() {
    console.log('glob2_then')
})

glob2_nextTick與Promise任務(wù)分別進(jìn)入各自的隊(duì)列
glob2_nextTick與Promise任務(wù)分別進(jìn)入各自的隊(duì)列

第八步:再次遇到setImmediate。

setImmediate(function() {
    console.log('immediate2');
    process.nextTick(function() {
        console.log('immediate2_nextTick');
    })
    new Promise(function(resolve) {
        console.log('immediate2_promise');
        resolve();
    }).then(function() {
        console.log('immediate2_then')
    })
})

nextTick
nextTick

這個(gè)時(shí)候,script中的代碼就執(zhí)行完畢了,執(zhí)行過(guò)程中,遇到不同的任務(wù)分發(fā)器,就將任務(wù)分發(fā)到各自對(duì)應(yīng)的隊(duì)列中去。接下來(lái),將會(huì)執(zhí)行所有的微任務(wù)隊(duì)列中的任務(wù)。

其中,nextTick隊(duì)列會(huì)比Promie先執(zhí)行。nextTick中的可執(zhí)行任務(wù)執(zhí)行完畢之后,才會(huì)開(kāi)始執(zhí)行Promise隊(duì)列中的任務(wù)。

當(dāng)所有可執(zhí)行的微任務(wù)執(zhí)行完畢之后,這一輪循環(huán)就表示結(jié)束了。下一輪循環(huán)繼續(xù)從宏任務(wù)隊(duì)列開(kāi)始執(zhí)行。

這個(gè)時(shí)候,script已經(jīng)執(zhí)行完畢,所以就從setTimeout隊(duì)列開(kāi)始執(zhí)行。

第二輪循環(huán)初始狀態(tài)
第二輪循環(huán)初始狀態(tài)

setTimeout任務(wù)的執(zhí)行,也依然是借助函數(shù)調(diào)用棧來(lái)完成,并且遇到任務(wù)分發(fā)器的時(shí)候也會(huì)將任務(wù)分發(fā)到對(duì)應(yīng)的隊(duì)列中去。

只有當(dāng)setTimeout中所有的任務(wù)執(zhí)行完畢之后,才會(huì)再次開(kāi)始執(zhí)行微任務(wù)隊(duì)列。并且清空所有的可執(zhí)行微任務(wù)。

setTiemout隊(duì)列產(chǎn)生的微任務(wù)執(zhí)行完畢之后,循環(huán)則回過(guò)頭來(lái)開(kāi)始執(zhí)行setImmediate隊(duì)列。仍然是先將setImmediate隊(duì)列中的任務(wù)執(zhí)行完畢,再執(zhí)行所產(chǎn)生的微任務(wù)。

當(dāng)setImmediate隊(duì)列執(zhí)行產(chǎn)生的微任務(wù)全部執(zhí)行之后,第二輪循環(huán)也就結(jié)束了

// 用數(shù)組模擬一個(gè)隊(duì)列
var tasks = [];

// 模擬一個(gè)事件分發(fā)器
var addFn1 = function(task) {
    tasks.push(task);
}

// 執(zhí)行所有的任務(wù)
var flush = function() {
    tasks.map(function(task) {
        task();
    })
}

// 最后利用setTimeout/或者其他你認(rèn)為合適的方式丟入事件循環(huán)中
setTimeout(function() {
    flush();
})

// 當(dāng)然,也可以不用丟進(jìn)事件循環(huán),而是我們自己手動(dòng)在適當(dāng)?shù)臅r(shí)機(jī)去執(zhí)行對(duì)應(yīng)的某一個(gè)方法

var dispatch = function(name) {
    tasks.map(function(item) {
        if(item.name == name) {
            item.handler();
        }
    })
}

// 當(dāng)然,我們把任務(wù)丟進(jìn)去的時(shí)候,多保存一個(gè)name即可。
// 這時(shí)候,task的格式就如下
demoTask =  {
    name: 'demo',
    handler: function() {}
}

// 于是,一個(gè)訂閱-通知的設(shè)計(jì)模式就這樣輕松的被實(shí)現(xiàn)了

最終結(jié)果:

golb1
glob1_promise
glob2_promise
glob1_nextTick
glob2_nextTick
glob1_then
glob2_then
timeout1
timeout1_promise
timeout2
timeout2_promise
timeout1_nextTick
timeout2_nextTick
timeout1_then
timeout2_then
immediate1
immediate1_promise
immediate2
immediate2_promise
immediate1_nextTick
immediate2_nextTick
immediate1_then
immediate2_then

總結(jié)一下:雖然宏任務(wù)總是比微任務(wù)先執(zhí)行,但是我們往往會(huì)忽略script這個(gè)宏任務(wù),所以實(shí)際上最開(kāi)始的宏任務(wù)會(huì)比微任務(wù)后執(zhí)行,promise構(gòu)造函數(shù)里的函數(shù)不會(huì)在隊(duì)列中,而是直接執(zhí)行,他跟隨promise這個(gè)任務(wù)分發(fā)器一樣會(huì)立即執(zhí)行,而then中的函數(shù)會(huì)進(jìn)入微任務(wù)隊(duì)列

這個(gè)前端面試在搞事

80% 應(yīng)聘者都不及格的 JS 面試題

回到正題,那么什么是異步呢,眾所周知,js是單線程的語(yǔ)言,腦袋一根筋,對(duì)于拿到的程序,一行一行的執(zhí)行,上面的執(zhí)行為完成,就傻傻的等著

var i, t = Date.now()
for (i = 0; i < 100000000; i++) {
}
console.log(Date.now() - t)  // 274(chrome瀏覽器)

執(zhí)行程序這樣沒(méi)有問(wèn)題,但是對(duì)于 JS 最初使用的環(huán)境 ———— 瀏覽器客戶端 ———— 就不一樣了。因此在瀏覽器端運(yùn)行的 js ,可能會(huì)有大量的網(wǎng)絡(luò)請(qǐng)求,而一個(gè)網(wǎng)絡(luò)資源啥時(shí)候返回,這個(gè)時(shí)間是不可預(yù)估的。這種情況也要傻傻的等著、卡頓著、啥都不做嗎?———— 那肯定不行。

因此,JS 對(duì)于這種場(chǎng)景就設(shè)計(jì)了異步 ———— 即,發(fā)起一個(gè)網(wǎng)絡(luò)請(qǐng)求,就先不管這邊了,先干其他事兒,網(wǎng)絡(luò)請(qǐng)求啥時(shí)候返回結(jié)果,到時(shí)候再說(shuō)。這樣就能保證一個(gè)網(wǎng)頁(yè)的流程運(yùn)行

同步:一件事接著一件事做,上一件沒(méi)做完,下一件事也不做,連續(xù)的執(zhí)行
異步:就是一個(gè)任務(wù)分成兩段,先執(zhí)行第一段,然后轉(zhuǎn)而執(zhí)行其他任務(wù),等做好了準(zhǔn)備,再回過(guò)頭執(zhí)行第二段,不連續(xù)的執(zhí)行

JavaScript 語(yǔ)言對(duì)異步編程的實(shí)現(xiàn),就是回調(diào)函數(shù)。所謂回調(diào)函數(shù),就是把任務(wù)的第二段單獨(dú)寫在一個(gè)函數(shù)里面,等到重新執(zhí)行這個(gè)任務(wù)的時(shí)候,就直接調(diào)用這個(gè)函數(shù)

f1(f2)

回調(diào)函數(shù)就是將函數(shù)f2當(dāng)做參數(shù)傳給f1,f1執(zhí)行之后才執(zhí)行f2,f2就是f1的回調(diào)函數(shù),所有的異步原理都是基于回調(diào)函數(shù),無(wú)論是promise還是async,他們都只是回調(diào)函數(shù)的語(yǔ)法糖。(回調(diào)函數(shù)|事件監(jiān)聽(tīng)|發(fā)布/訂閱|Promise)

事件綁定與異步操作原理相似,但是它與異步有兩個(gè)不同的地方

1.event-loop 執(zhí)行時(shí),調(diào)用的源不一樣。異步操作是系統(tǒng)自動(dòng)調(diào)用,無(wú)論是setTimeout時(shí)間到了還是$.ajax請(qǐng)求返回了,系統(tǒng)會(huì)自動(dòng)調(diào)用。而事件綁定就需要用戶手動(dòng)觸發(fā)

2.從設(shè)計(jì)上來(lái)將,事件綁定有著明顯的“訂閱-發(fā)布”的設(shè)計(jì)模式,而異步操作卻沒(méi)有

開(kāi)發(fā)中比較常用的異步操作有:

網(wǎng)絡(luò)請(qǐng)求,如ajax http.get
IO 操作,如readFile readdir
定時(shí)函數(shù),如setTimeout setInterval

promise:
先把規(guī)范理解一下,再來(lái)講講它的api,最后實(shí)現(xiàn)一個(gè)promise來(lái)徹底理解。

  • Promise 本質(zhì)是一個(gè)狀態(tài)機(jī)。每個(gè) promise 只能是 3 種狀態(tài)中的一種:pending、fulfilled 或 rejected。狀態(tài)轉(zhuǎn)變只能是 pending -> fulfilled 或者 pending -> rejected。狀態(tài)轉(zhuǎn)變不可逆。

  • then 方法可以被同一個(gè) promise 調(diào)用多次,then方法返回一個(gè)新的Promise

  • then 方法必須返回一個(gè) promise。規(guī)范里沒(méi)有明確說(shuō)明返回一個(gè)新的 promise 還是復(fù)用老的 promise(即 return this),大多數(shù)實(shí)現(xiàn)都是返回一個(gè)新的 promise,而且復(fù)用老的 promise 可能改變內(nèi)部狀態(tài),這與規(guī)范也是相違背的。

  • 值穿透

  • 只有一個(gè)then方法,沒(méi)有catch,race,all等方法,甚至沒(méi)有構(gòu)造函數(shù)

Promise標(biāo)準(zhǔn)中僅指定了Promise對(duì)象的then方法的行為,其它一切我們常見(jiàn)的方法/函數(shù)都并沒(méi)有指定,包括catch,race,all等常用方法,甚至也沒(méi)有指定該如何構(gòu)造出一個(gè)Promise對(duì)象

  • 不同Promise的實(shí)現(xiàn)需要可以相互調(diào)用

Promise的構(gòu)造函數(shù)接收一個(gè)參數(shù),是函數(shù),并且傳入兩個(gè)參數(shù):resolve,reject,分別表示異步操作執(zhí)行成功后的回調(diào)函數(shù)和異步操作執(zhí)行失敗后的回調(diào)函數(shù)。其實(shí)這里用“成功”和“失敗”來(lái)描述并不準(zhǔn)確,按照標(biāo)準(zhǔn)來(lái)講,resolve是將Promise的狀態(tài)置為fullfiled,reject是將Promise的狀態(tài)置為rejected

var p = new Promise(function(resolve, reject){
    //做一些異步操作
    setTimeout(function(){
        console.log('執(zhí)行完成');
        resolve('隨便什么數(shù)據(jù)');
    }, 2000);
});

//執(zhí)行完成

運(yùn)行代碼,會(huì)在2秒后輸出“執(zhí)行完成”。注意!我只是new了一個(gè)對(duì)象,并沒(méi)有調(diào)用它,我們傳進(jìn)去的函數(shù)就已經(jīng)執(zhí)行了,這是需要注意的一個(gè)細(xì)節(jié)。所以我們用Promise的時(shí)候一般是包在一個(gè)函數(shù)中,在需要的時(shí)候去運(yùn)行這個(gè)函數(shù)

function runAsync(){
    var p = new Promise(function(resolve, reject){
        //做一些異步操作
        setTimeout(function(){
            console.log('執(zhí)行完成');
            resolve('隨便什么數(shù)據(jù)');
        }, 2000);
    });
    return p;            
}
runAsync()

在我們包裝好的函數(shù)最后,會(huì)return出Promise對(duì)象,也就是說(shuō),執(zhí)行這個(gè)函數(shù)我們得到了一個(gè)Promise對(duì)象。還記得Promise對(duì)象上有then、catch方法吧?這就是強(qiáng)大之處了

function runAsync(){
    var p = new Promise(function(resolve, reject){
        //做一些異步操作
        setTimeout(function(){
            console.log('執(zhí)行完成');
            resolve('隨便什么數(shù)據(jù)');
        }, 2000);
    });
    return p;            
}

runAsync().then(function(data){
    console.log(data);
    //后面可以用傳過(guò)來(lái)的數(shù)據(jù)做些其他操作
    //......
});

//執(zhí)行完成
//隨便什么數(shù)據(jù)

在runAsync()的返回上直接調(diào)用then方法,then接收一個(gè)參數(shù),是函數(shù),并且會(huì)拿到我們?cè)趓unAsync中調(diào)用resolve時(shí)傳的的參數(shù)。運(yùn)行這段代碼,會(huì)在2秒后輸出“執(zhí)行完成”,緊接著輸出“隨便什么數(shù)據(jù)

這時(shí)候你應(yīng)該有所領(lǐng)悟了,原來(lái)then里面的函數(shù)就跟我們平時(shí)的回調(diào)函數(shù)一個(gè)意思,能夠在runAsync這個(gè)異步任務(wù)執(zhí)行完成之后被執(zhí)行。這就是Promise的作用了,簡(jiǎn)單來(lái)講,就是能把原來(lái)的回調(diào)寫法分離出來(lái),在異步操作執(zhí)行完后,用鏈?zhǔn)秸{(diào)用的方式執(zhí)行回調(diào)函數(shù)

從表面上看,Promise只是能夠簡(jiǎn)化層層回調(diào)的寫法,而實(shí)質(zhì)上,Promise的精髓是“狀態(tài)”,用維護(hù)狀態(tài)、傳遞狀態(tài)的方式來(lái)使得回調(diào)函數(shù)能夠及時(shí)調(diào)用,它比傳遞callback函數(shù)要簡(jiǎn)單、靈活的多。所以使用Promise的正確場(chǎng)景是這樣的:

function runAsync1(){
    var p = new Promise(function(resolve, reject){
        //做一些異步操作
        setTimeout(function(){
            console.log('異步任務(wù)1執(zhí)行完成');
            resolve('隨便什么數(shù)據(jù)1');
        }, 1000);
    });
    return p;            
}
function runAsync2(){
    var p = new Promise(function(resolve, reject){
        //做一些異步操作
        setTimeout(function(){
            console.log('異步任務(wù)2執(zhí)行完成');
            resolve('隨便什么數(shù)據(jù)2');
        }, 2000);
    });
    return p;            
}
function runAsync3(){
    var p = new Promise(function(resolve, reject){
        //做一些異步操作
        setTimeout(function(){
            console.log('異步任務(wù)3執(zhí)行完成');
            resolve('隨便什么數(shù)據(jù)3');
        }, 2000);
    });
    return p;            
}

runAsync1()
.then(function(data){
    console.log(data);
    return runAsync2();
})
.then(function(data){
    console.log(data);
    return runAsync3();
})
.then(function(data){
    console.log(data);
});

//異步任務(wù)1執(zhí)行完成
//隨便什么數(shù)據(jù)1
//異步任務(wù)2執(zhí)行完成
//隨便什么數(shù)據(jù)2
//異步任務(wù)3執(zhí)行完成
//隨便什么數(shù)據(jù)3

我們光用了resolve,還沒(méi)用reject呢,它是做什么的呢?事實(shí)上,我們前面的例子都是只有“執(zhí)行成功”的回調(diào),還沒(méi)有“失敗”的情況,reject的作用就是把Promise的狀態(tài)置為rejected,這樣我們?cè)趖hen中就能捕捉到,然后執(zhí)行“失敗”情況的回調(diào)

function getNumber(){
    var p = new Promise(function(resolve, reject){
        //做一些異步操作
        setTimeout(function(){
            var num = Math.ceil(Math.random()*10); //生成1-10的隨機(jī)數(shù)
            if(num<=5){
                resolve(num);
            }
            else{
                reject('數(shù)字太大了');
            }
        }, 2000);
    });
    return p;            
}

getNumber()
.then(
    function(data){
        console.log('resolved');
        console.log(data);
    }, 
    function(reason, data){
        console.log('rejected');
        console.log(reason);
    }
);

getNumber函數(shù)用來(lái)異步獲取一個(gè)數(shù)字,2秒后執(zhí)行完成,如果數(shù)字小于等于5,我們認(rèn)為是“成功”了,調(diào)用resolve修改Promise的狀態(tài)。否則我們認(rèn)為是“失敗”了,調(diào)用reject并傳遞一個(gè)參數(shù),作為失敗的原因。

運(yùn)行g(shù)etNumber并且在then中傳了兩個(gè)參數(shù),then方法可以接受兩個(gè)參數(shù),第一個(gè)對(duì)應(yīng)resolve的回調(diào),第二個(gè)對(duì)應(yīng)reject的回調(diào)。所以我們能夠分別拿到他們傳過(guò)來(lái)的數(shù)據(jù)。多次運(yùn)行這段代碼,你會(huì)隨機(jī)得到下面兩種結(jié)果

resolver 2
||
rejected 太大了

我們知道Promise對(duì)象除了then方法,還有一個(gè)catch方法,它是做什么用的呢?其實(shí)它和then的第二個(gè)參數(shù)一樣,用來(lái)指定reject的回調(diào),用法是這樣:

getNumber()
.then(function(data){
    console.log('resolved');
    console.log(data);
})
.catch(function(reason){
    console.log('rejected');
    console.log(reason);
});

效果和寫在then的第二個(gè)參數(shù)里面一樣,相當(dāng)于then(null,fn)。不過(guò)它還有另外一個(gè)作用:在執(zhí)行resolve的回調(diào)(也就是上面then中的第一個(gè)參數(shù))時(shí),如果拋出異常了(代碼出錯(cuò)了),那么并不會(huì)報(bào)錯(cuò)卡死js,而是會(huì)進(jìn)到這個(gè)catch方法中,所以我們要多用catch少用rejected

getNumber()
.then(function(data){
    console.log('resolved');
    console.log(data);
    console.log(somedata); //此處的somedata未定義
})
.catch(function(reason){
    console.log('rejected');
    console.log(reason);
});

在resolve的回調(diào)中,我們console.log(somedata);而somedata這個(gè)變量是沒(méi)有被定義的。如果我們不用Promise,代碼運(yùn)行到這里就直接在控制臺(tái)報(bào)錯(cuò)了,不往下運(yùn)行了。但是在這里,會(huì)得到這樣的結(jié)果:

resolved
4
rejected
somedata is not defined

也就是說(shuō)進(jìn)到catch方法里面去了,而且把錯(cuò)誤原因傳到了reason參數(shù)中。即便是有錯(cuò)誤的代碼也不會(huì)報(bào)錯(cuò)了,這與我們的try/catch語(yǔ)句有相同的功能

Promise的all方法提供了并行執(zhí)行異步操作的能力,并且在所有異步操作執(zhí)行完后才執(zhí)行回調(diào)。我們?nèi)耘f使用上面定義好的runAsync1、runAsync2、runAsync3這三個(gè)函數(shù),看下面的例子:

Promise
.all([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
    console.log(results);
});

用Promise.all來(lái)執(zhí)行,all接收一個(gè)數(shù)組參數(shù),里面的值最終都算返回Promise對(duì)象。這樣,三個(gè)異步操作的并行執(zhí)行的,等到它們都執(zhí)行完后才會(huì)進(jìn)到then里面。那么,三個(gè)異步操作返回的數(shù)據(jù)哪里去了呢?都在then里面呢,all會(huì)把所有異步操作的結(jié)果放進(jìn)一個(gè)數(shù)組中傳給then,就是上面的results。所以上面代碼的輸出結(jié)果就是:

//異步任務(wù)1執(zhí)行完成
//異步任務(wù)2執(zhí)行完成
//異步任務(wù)3執(zhí)行完成
//[隨便什么數(shù)據(jù)1,隨便什么數(shù)據(jù)2,隨便什么數(shù)據(jù)3]

有了all,你就可以并行執(zhí)行多個(gè)異步操作,并且在一個(gè)回調(diào)中處理所有的返回?cái)?shù)據(jù),是不是很酷?有一個(gè)場(chǎng)景是很適合用這個(gè)的,一些游戲類的素材比較多的應(yīng)用,打開(kāi)網(wǎng)頁(yè)時(shí),預(yù)先加載需要用到的各種資源如圖片、flash以及各種靜態(tài)文件。所有的都加載完后,我們?cè)龠M(jìn)行頁(yè)面的初始化

ll方法的效果實(shí)際上是「誰(shuí)跑的慢,以誰(shuí)為準(zhǔn)執(zhí)行回調(diào)」,那么相對(duì)的就有另一個(gè)方法「誰(shuí)跑的快,以誰(shuí)為準(zhǔn)執(zhí)行回調(diào)」,這就是race方法,這個(gè)詞本來(lái)就是賽跑的意思。race的用法與all一樣,我們把上面runAsync1的延時(shí)改為1秒來(lái)看一下

Promise
.race([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
    console.log(results);
});

這三個(gè)異步操作同樣是并行執(zhí)行的。結(jié)果你應(yīng)該可以猜到,1秒后runAsync1已經(jīng)執(zhí)行完了,此時(shí)then里面的就執(zhí)行了。結(jié)果是這樣的

//異步任務(wù)1執(zhí)行完成
//隨便什么數(shù)據(jù)1
//異步任務(wù)2執(zhí)行完成
//異步任務(wù)3執(zhí)行完成

在then里面的回調(diào)開(kāi)始執(zhí)行時(shí),runAsync2()和runAsync3()并沒(méi)有停止,仍舊再執(zhí)行。于是再過(guò)1秒后,輸出了他們結(jié)束的標(biāo)志。

這個(gè)race有什么用呢?使用場(chǎng)景還是很多的,比如我們可以用race給某個(gè)異步請(qǐng)求設(shè)置超時(shí)時(shí)間,并且在超時(shí)后執(zhí)行相應(yīng)的操作,代碼如下:

function requestImg(){
    var p = new Promise(function(resolve, reject){
        var img = new Image();
        img.onload = function(){
            resolve(img);
        }
        img.src = 'xxxxxx';
    });
    return p;
}

//延時(shí)函數(shù),用于給請(qǐng)求計(jì)時(shí)
function timeout(){
    var p = new Promise(function(resolve, reject){
        setTimeout(function(){
            reject('圖片請(qǐng)求超時(shí)');
        }, 5000);
    });
    return p;
}

Promise
.race([requestImg(), timeout()])
.then(function(results){
    console.log(results);
})
.catch(function(reason){
    console.log(reason);
});

requestImg函數(shù)會(huì)異步請(qǐng)求一張圖片,我把地址寫為"xxxxxx",所以肯定是無(wú)法成功請(qǐng)求到的。timeout函數(shù)是一個(gè)延時(shí)5秒的異步操作。我們把這兩個(gè)返回Promise對(duì)象的函數(shù)放進(jìn)race,于是他倆就會(huì)賽跑,如果5秒之內(nèi)圖片請(qǐng)求成功了,那么遍進(jìn)入then方法,執(zhí)行正常的流程。如果5秒鐘圖片還未成功返回,那么timeout就跑贏了,則進(jìn)入catch,報(bào)出“圖片請(qǐng)求超時(shí)”的信息

搞懂了這些就基本理解了promise,以下八句話更能理解到promsie的精髓:

  1. Promise的立即執(zhí)行性
var p = new Promise(function(resolve, reject){
  console.log("create a promise");
  resolve("success");
});

console.log("after new Promise");

p.then(function(value){
  console.log(value);
});
// create a promise
// after new Promise
// value

Promise對(duì)象表示未來(lái)某個(gè)將要發(fā)生的事件,但在創(chuàng)建(new)Promise時(shí),作為Promise參數(shù)傳入的函數(shù)是會(huì)被立即執(zhí)行的,只是其中執(zhí)行的代碼可以是異步代碼。有些同學(xué)會(huì)認(rèn)為,當(dāng)Promise對(duì)象調(diào)用then方法時(shí),Promise接收的函數(shù)才會(huì)執(zhí)行,這是錯(cuò)誤的。因此,代碼中"create a promise"先于"after new Promise"輸出

2.Promise 三種狀態(tài)

var p1 = new Promise(function(resolve,reject){
  resolve(1);
});
//p1 的函數(shù)參數(shù)中執(zhí)行的是一段同步代碼,Promise剛創(chuàng)建完成,resolve方法就已經(jīng)被調(diào)用了,因而緊跟著的輸出顯示p1是resolved狀態(tài)

var p2 = new Promise(function(resolve,reject){
  setTimeout(function(){
    resolve(2);  
  }, 500);      
});
var p3 = new Promise(function(resolve,reject){
  setTimeout(function(){
    reject(3);  
  }, 500);      
});

console.log(p1);
console.log(p2);
console.log(p3);
setTimeout(function(){
  console.log(p2);
}, 1000);
setTimeout(function(){
  console.log(p3);
}, 1000);

p1.then(function(value){
  console.log(value);
});
p2.then(function(value){
  console.log(value);
});
p3.catch(function(err){
  console.log(err);
});

Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 1}
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
1
2
3
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 2}
Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: 3}

Promise的內(nèi)部實(shí)現(xiàn)是一個(gè)狀態(tài)機(jī)。Promise有三種狀態(tài):pending,resolved,rejected。當(dāng)Promise剛創(chuàng)建完成時(shí),處于pending狀態(tài);當(dāng)Promise中的函數(shù)參數(shù)執(zhí)行了resolve后,Promise由pending狀態(tài)變成resolved狀態(tài);如果在Promise的函數(shù)參數(shù)中執(zhí)行的不是resolve方法,而是reject方法,那么Promise會(huì)由pending狀態(tài)變成rejected狀態(tài)。

p2、p3剛創(chuàng)建完成時(shí),控制臺(tái)輸出的這兩臺(tái)Promise都處于pending狀態(tài),但為什么p1是resolved狀態(tài)呢? 這是因?yàn)閜1 的函數(shù)參數(shù)中執(zhí)行的是一段同步代碼,Promise剛創(chuàng)建完成,resolve方法就已經(jīng)被調(diào)用了,因而緊跟著的輸出顯示p1是resolved狀態(tài)。我們通過(guò)兩個(gè)setTimeout函數(shù),延遲1s后再次輸出p2、p3的狀態(tài),此時(shí)p2、p3已經(jīng)執(zhí)行完成,狀態(tài)分別變成resolved和rejected

分別分析就看得很清楚了

var p1 = new Promise(function(resolve,reject){
  resolve(1);
});
console.log(p1);
p1.then(function(value){
  console.log(value);
});
//
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 1}
1
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: undefined}
var p2 = new Promise(function(resolve,reject){
  setTimeout(function(){
    resolve(2);  
  }, 500);      
});
console.log(p2);
setTimeout(function(){
  console.log(p2);
}, 1000);
p2.then(function(value){
  console.log(value);
});

//
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
2
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 2}
var p3 = new Promise(function(resolve,reject){
  setTimeout(function(){
    reject(3);  
  }, 500);      
});
console.log(p3);
setTimeout(function(){
  console.log(p3);
}, 1000);
p3.then(function(value){
  console.log(value);
});

Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
3
Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: 2}
  1. Promise 狀態(tài)的不可逆性
var p1 = new Promise(function(resolve, reject){
  resolve("success1");
  resolve("success2");
});

var p2 = new Promise(function(resolve, reject){
  resolve("success");
  reject("reject");
});

p1.then(function(value){
  console.log(value);
});

p2.then(function(value){
  console.log(value);
});

"success1"
"success"

Promise狀態(tài)的一旦變成resolved或rejected時(shí),Promise的狀態(tài)和值就固定下來(lái)了,不論你后續(xù)再怎么調(diào)用resolve或reject方法,都不能改變它的狀態(tài)和值。因此,p1中resolve("success2")并不能將p1的值更改為success2,p2中reject("reject")也不能將p2的狀態(tài)由resolved改變?yōu)閞ejected.

  1. 鏈?zhǔn)秸{(diào)用
var p = new Promise(function(resolve, reject){
  resolve(1);
});
p.then(function(value){               //第一個(gè)then
  console.log(value);
  return value*2;
}).then(function(value){              //第二個(gè)then
  console.log(value);
}).then(function(value){              //第三個(gè)then
  console.log(value);
  return Promise.resolve('resolve'); 
}).then(function(value){              //第四個(gè)then
  console.log(value);
  return Promise.reject('reject');
}).then(function(value){              //第五個(gè)then
  console.log('resolve: '+ value);
}, function(err){
  console.log('reject: ' + err);
})
1
2
undefined
"resolve"
"reject: reject"

Promise對(duì)象的then方法返回一個(gè)新的Promise對(duì)象,因此可以通過(guò)鏈?zhǔn)秸{(diào)用then方法。then方法接收兩個(gè)函數(shù)作為參數(shù),第一個(gè)參數(shù)是Promise執(zhí)行成功時(shí)的回調(diào),第二個(gè)參數(shù)是Promise執(zhí)行失敗時(shí)的回調(diào)。兩個(gè)函數(shù)只會(huì)有一個(gè)被調(diào)用,函數(shù)的返回值將被用作創(chuàng)建then返回的Promise對(duì)象。這兩個(gè)參數(shù)的返回值可以是以下三種情況中的一種:

  • return 一個(gè)同步的值 ,或者 undefined(當(dāng)沒(méi)有返回一個(gè)有效值時(shí),默認(rèn)返回undefined),then方法將返回一個(gè)resolved狀態(tài)的Promise對(duì)象,Promise對(duì)象的值就是這個(gè)返回值。

  • return 另一個(gè) Promise,then方法將根據(jù)這個(gè)Promise的狀態(tài)和值創(chuàng)建一個(gè)新的Promise對(duì)象返回。

  • throw 一個(gè)同步異常,then方法將返回一個(gè)rejected狀態(tài)的Promise, 值是該異常

根據(jù)以上分析,代碼中第一個(gè)then會(huì)返回一個(gè)值為2(1*2),狀態(tài)為resolved的Promise對(duì)象,于是第二個(gè)then輸出的值是2。第二個(gè)then中沒(méi)有返回值,因此將返回默認(rèn)的undefined,于是在第三個(gè)then中輸出undefined。第三個(gè)then和第四個(gè)then中分別返回一個(gè)狀態(tài)是resolved的Promise和一個(gè)狀態(tài)是rejected的Promise,依次由第四個(gè)then中成功的回調(diào)函數(shù)和第五個(gè)then中失敗的回調(diào)函數(shù)處理

  1. Promise then() 回調(diào)異步性
var p = new Promise(function(resolve, reject){
  resolve("success");
});

p.then(function(value){
  console.log(value);
});

console.log("which one is called first ?");

"which one is called first ?"
"success"

Promise接收的函數(shù)參數(shù)是同步執(zhí)行的,但then方法中的回調(diào)函數(shù)執(zhí)行則是異步的,因此,"success"會(huì)在后面輸出

6.Promise 中的異常

var p1 = new Promise( function(resolve,reject){
  foo.bar();
  resolve( 1 );      
});

p1.then(
  function(value){
    console.log('p1 then value: ' + value);
  },
  function(err){
    console.log('p1 then err: ' + err);
  }
).then(
  function(value){
    console.log('p1 then then value: '+value);
  },
  function(err){
    console.log('p1 then then err: ' + err);
  }
);

var p2 = new Promise(function(resolve,reject){
  resolve( 2 );    
});

p2.then(
  function(value){
    console.log('p2 then value: ' + value);
    foo.bar();
  }, 
  function(err){
    console.log('p2 then err: ' + err);
  }
).then(
  function(value){
    console.log('p2 then then value: ' + value);
  },
  function(err){
    console.log('p2 then then err: ' + err);
    return 1;
  }
).then(
  function(value){
    console.log('p2 then then then value: ' + value);
  },
  function(err){
    console.log('p2 then then then err: ' + err);
  }
);

///
p1 then err: ReferenceError: foo is not defined
p2 then value: 2
p1 then then value: undefined
p2 then then err: ReferenceError: foo is not defined
p2 then then then value: 1

Promise中的異常由then參數(shù)中第二個(gè)回調(diào)函數(shù)(Promise執(zhí)行失敗的回調(diào))處理,異常信息將作為Promise的值。異常一旦得到處理,then返回的后續(xù)Promise對(duì)象將恢復(fù)正常,并會(huì)被Promise執(zhí)行成功的回調(diào)函數(shù)處理。另外,需要注意p1、p2 多級(jí)then的回調(diào)函數(shù)是交替執(zhí)行的 ,這正是由Promise then回調(diào)的異步性決定的

7.Promise.resolve()

var p1 = Promise.resolve( 1 );
var p2 = Promise.resolve( p1 );
var p3 = new Promise(function(resolve, reject){
  resolve(1);
});
var p4 = new Promise(function(resolve, reject){
  resolve(p1);
});

console.log(p1 === p2); 
console.log(p1 === p3);
console.log(p1 === p4);
console.log(p3 === p4);

p4.then(function(value){
  console.log('p4=' + value);
});

p2.then(function(value){
  console.log('p2=' + value);
})

p1.then(function(value){
  console.log('p1=' + value);
})

////
true
false
false
false
p2=1
p1=1
p4=1
  1. resolve vs reject
var p1 = new Promise(function(resolve, reject){
  resolve(Promise.resolve('resolve'));
});

var p2 = new Promise(function(resolve, reject){
  resolve(Promise.reject('reject'));
});

var p3 = new Promise(function(resolve, reject){
  reject(Promise.resolve('resolve'));
});

p1.then(
  function fulfilled(value){
    console.log('fulfilled: ' + value);
  }, 
  function rejected(err){
    console.log('rejected: ' + err);
  }
);

p2.then(
  function fulfilled(value){
    console.log('fulfilled: ' + value);
  }, 
  function rejected(err){
    console.log('rejected: ' + err);
  }
);

p3.then(
  function fulfilled(value){
    console.log('fulfilled: ' + value);
  }, 
  function rejected(err){
    console.log('rejected: ' + err);
  }
);

////
p3 rejected: [object Promise]
p1 fulfilled: resolve
p2 rejected: reject

拆分:

var p1 = new Promise(function(resolve, reject){
  resolve(Promise.resolve('resolve'));
});
p1.then(
  function fulfilled(value){
    console.log('fulfilled: ' + value);
  }, 
  function rejected(err){
    console.log('rejected: ' + err);
  }
);
fulfilled: resolve

Promise回調(diào)函數(shù)中的第一個(gè)參數(shù)resolve,會(huì)對(duì)Promise執(zhí)行"拆箱"動(dòng)作。即當(dāng)resolve的參數(shù)是一個(gè)Promise對(duì)象時(shí),resolve會(huì)"拆箱"獲取這個(gè)Promise對(duì)象的狀態(tài)和值,但這個(gè)過(guò)程是異步的。p1"拆箱"后,獲取到Promise對(duì)象的狀態(tài)是resolved,因此fulfilled回調(diào)被執(zhí)行

var p2 = new Promise(function(resolve, reject){
  resolve(Promise.reject('reject'));
});
p2.then(
  function fulfilled(value){
    console.log('fulfilled: ' + value);
  }, 
  function rejected(err){
    console.log('rejected: ' + err);
  }
);
//
rejected: reject
var p3 = new Promise(function(resolve, reject){
  reject(Promise.resolve('resolve'));
  //reject的參數(shù)會(huì)直接傳遞給then方法中的rejected回調(diào)
});
p3.then(
  function fulfilled(value){
    console.log('fulfilled: ' + value);
  }, 
  function rejected(err){
    console.log('rejected: ' + err);
  }
);
rejected: [object Promise]

p2"拆箱"后,獲取到Promise對(duì)象的狀態(tài)是rejected,因此rejected回調(diào)被執(zhí)行。但Promise回調(diào)函數(shù)中的第二個(gè)參數(shù)reject不具備”拆箱“的能力,reject的參數(shù)會(huì)直接傳遞給then方法中的rejected回調(diào)。因此,即使p3 reject接收了一個(gè)resolved狀態(tài)的Promise,then方法中被調(diào)用的依然是rejected,并且參數(shù)就是reject接收到的Promise對(duì)象

promise面試題:紅燈三秒亮一次,綠燈一秒亮一次,黃燈2秒亮一次;如何讓三個(gè)燈不斷交替重復(fù)亮燈?(用Promse實(shí)現(xiàn)) 三個(gè)亮燈函數(shù)已經(jīng)存在: function red(){ console.log('red'); } function green(){ console.log('green'); } function yellow(){ console.log('yellow'); }

setTimeout相關(guān)的異步隊(duì)列會(huì)掛起直到主進(jìn)程空閑。如果使用while無(wú)限循環(huán),主進(jìn)程永遠(yuǎn)不會(huì)空閑,setTimeout的函數(shù)永遠(yuǎn)不會(huì)執(zhí)行!

function red(){
    console.log('red');
}
function green(){
    console.log('green');
}
function yellow(){
    console.log('yellow');
}

var tic = function(timmer, cb){
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            cb();
            resolve();
        }, timmer);
    });
};

var d = new Promise(function(resolve, reject){resolve();});
var step = function(def) {
    def.then(function(){
        return tic(3000, red);
    }).then(function(){
        return tic(2000, green);
    }).then(function(){
        return tic(1000, yellow);
    }).then(function(){
        step(def);
    });
}

step(d);
var tic = function(timmer, str){
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            console.log(str);
            resolve(1);
        }, timmer);
    });
};


function *gen(){
    yield tic(3000, 'red');
    yield tic(1000, 'green');
    yield tic(2000, 'yellow');
}

var iterator = gen();
var step = function(gen, iterator){
    var s = iterator.next();
    if (s.done) {
        step(gen, gen());
    } else {
        s.value.then(function() {
            step(gen, iterator);
        });
    }
}

step(gen, iterator);

使用then方法添加回調(diào)函數(shù):

// 寫法一
doSomething().then(function () {
  return doSomethingElse();
});

// 寫法二
doSomething().then(function () {
  doSomethingElse();
});

// 寫法三
doSomething().then(doSomethingElse());

// 寫法四
doSomething().then(doSomethingElse);

為了便于講解,下面這四種寫法都再用then方法接一個(gè)回調(diào)函數(shù)finalHandler。寫法一的finalHandler回調(diào)函數(shù)的參數(shù),是doSomethingElse函數(shù)的運(yùn)行結(jié)果

doSomething().then(function () {
  return doSomethingElse();
}).then(finalHandler);

寫法二的finalHandler回調(diào)函數(shù)的參數(shù)是undefined

doSomething().then(function () {
  doSomethingElse();
  return;
}).then(finalHandler);

寫法三的finalHandler回調(diào)函數(shù)的參數(shù),是doSomethingElse函數(shù)返回的回調(diào)函數(shù)的運(yùn)行結(jié)果

doSomething().then(doSomethingElse())
.then(finalHandler);

寫法四與寫法一只有一個(gè)差別,那就是doSomethingElse會(huì)接收到doSomething()返回的結(jié)果

doSomething().then(doSomethingElse)
.then(finalHandler);

實(shí)現(xiàn)一個(gè)promise

var Promise = (function() {
  function Promise(resolver) {
    if (typeof resolver !== 'function') {
      throw new TypeError('Promise resolver ' + resolver + ' is not a function')
    }
    if (!(this instanceof Promise)) return new Promise(resolver)

    var self = this
    self.callbacks = []
    self.status = 'pending'

    function resolve(value) {
      if (value instanceof Promise) {
        return value.then(resolve, reject)
      }
      setTimeout(function() {
        if (self.status !== 'pending') {
          return
        }
        self.status = 'resolved'
        self.data = value

        for (var i = 0; i < self.callbacks.length; i++) {
          self.callbacks[i].onResolved(value)
        }
      })
    }

    function reject(reason) {
      setTimeout(function(){
        if (self.status !== 'pending') {
          return
        }
        self.status = 'rejected'
        self.data = reason

        for (var i = 0; i < self.callbacks.length; i++) {
          self.callbacks[i].onRejected(reason)
        }
      })
    }

    try{
      resolver(resolve, reject)
    } catch(e) {
      reject(e)
    }
  }

  function resolvePromise(promise, x, resolve, reject) {
    var then
    var thenCalledOrThrow = false

    if (promise === x) {
      return reject(new TypeError('Chaining cycle detected for promise!'))
    }

    if (x instanceof Promise) {
      if (x.status === 'pending') {
        x.then(function(v) {
          resolvePromise(promise, v, resolve, reject);
        }, reject);
      } else {
        x.then(resolve, reject)
      }
      return
    }

    if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
      try {
        then = x.then
        if (typeof then === 'function') {
          then.call(x, function rs(y) {
            if (thenCalledOrThrow) return
            thenCalledOrThrow = true
            return resolvePromise(promise, y, resolve, reject)
          }, function rj(r) {
            if (thenCalledOrThrow) return
            thenCalledOrThrow = true
            return reject(r)
          })
        } else {
          return resolve(x)
        }
      } catch(e) {
        if (thenCalledOrThrow) return
        thenCalledOrThrow = true
        return reject(e)
      }
    } else {
      return resolve(x)
    }
  }

  Promise.prototype.then = function(onResolved, onRejected) {
    onResolved = typeof onResolved === 'function' ? onResolved : function(v){return v}
    onRejected = typeof onRejected === 'function' ? onRejected : function(r){throw r}
    var self = this
    var promise2

    if (self.status === 'resolved') {
      return promise2 = new Promise(function(resolve, reject) {
        setTimeout(function() {
          try {
            var value = onResolved(self.data)
            resolvePromise(promise2, value, resolve, reject)
          } catch(e) {
            return reject(e)
          }
        })
      })
    }

    if (self.status === 'rejected') {
      return promise2 = new Promise(function(resolve, reject) {
        setTimeout(function() {
          try {
            var value = onRejected(self.data)
            resolvePromise(promise2, value, resolve, reject)
          } catch(e) {
            return reject(e)
          }
        })
      })
    }

    if (self.status === 'pending') {
      return promise2 = new Promise(function(resolve, reject) {
        self.callbacks.push({
          onResolved: function(value) {
            try {
              var value = onResolved(value)
              resolvePromise(promise2, value, resolve, reject)
            } catch(e) {
              return reject(e)
            }
          },
          onRejected: function(reason) {
            try {
              var value = onRejected(reason)
              resolvePromise(promise2, value, resolve, reject)
            } catch(e) {
              return reject(e)
            }
          }
        })
      })
    }
  }

  Promise.prototype.valueOf = function() {
    return this.data
  }

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

  Promise.prototype.finally = function(fn) {
    // 為什么這里可以呢,因?yàn)樗械膖hen調(diào)用是一起的,但是這個(gè)then里調(diào)用fn又異步了一次,所以它總是最后調(diào)用的。
    // 當(dāng)然這里只能保證在已添加的函數(shù)里是最后一次,不過(guò)這也是必然。
    // 不過(guò)看起來(lái)比其它的實(shí)現(xiàn)要簡(jiǎn)單以及容易理解的多。
    // 貌似對(duì)finally的行為沒(méi)有一個(gè)公認(rèn)的定義,所以這個(gè)實(shí)現(xiàn)目前是跟Q保持一致,會(huì)返回一個(gè)新的Promise而不是原來(lái)那個(gè)。
    return this.then(function(v){
      setTimeout(fn)
      return v
    }, function(r){
      setTimeout(fn)
      throw r
    })
  }

  Promise.prototype.spread = function(fn, onRejected) {
    return this.then(function(values) {
      return fn.apply(null, values)
    }, onRejected)
  }

  Promise.prototype.inject = function(fn, onRejected) {
    return this.then(function(v) {
      return fn.apply(null, fn.toString().match(/\((.*?)\)/)[1].split(',').map(function(key){
        return v[key];
      }))
    }, onRejected)
  }

  Promise.prototype.delay = function(duration) {
    return this.then(function(value) {
      return new Promise(function(resolve, reject) {
        setTimeout(function() {
          resolve(value)
        }, duration)
      })
    }, function(reason) {
      return new Promise(function(resolve, reject) {
        setTimeout(function() {
          reject(reason)
        }, duration)
      })
    })
  }

  Promise.all = function(promises) {
    return new Promise(function(resolve, reject) {
      var resolvedCounter = 0
      var promiseNum = promises.length
      var resolvedValues = new Array(promiseNum)
      for (var i = 0; i < promiseNum; i++) {
        (function(i) {
          Promise.resolve(promises[i]).then(function(value) {
            resolvedCounter++
            resolvedValues[i] = value
            if (resolvedCounter == promiseNum) {
              return resolve(resolvedValues)
            }
          }, function(reason) {
            return reject(reason)
          })
        })(i)
      }
    })
  }

  Promise.race = function(promises) {
    return new Promise(function(resolve, reject) {
      for (var i = 0; i < promises.length; i++) {
        Promise.resolve(promises[i]).then(function(value) {
          return resolve(value)
        }, function(reason) {
          return reject(reason)
        })
      }
    })
  }

  Promise.resolve = function(value) {
    return new Promise(function(resolve) {
      resolve(value)
    })
  }

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

  Promise.fcall = function(fn){
    // 雖然fn可以接收到上一層then里傳來(lái)的參數(shù),但是其實(shí)是undefined,所以跟沒(méi)有是一樣的,因?yàn)閞esolve沒(méi)參數(shù)啊
    return Promise.resolve().then(fn)
  }

  Promise.done = Promise.stop = function(){
    return new Promise(function(){})
  }

  Promise.deferred = Promise.defer = function() {
    var dfd = {}
    dfd.promise = new Promise(function(resolve, reject) {
      dfd.resolve = resolve
      dfd.reject = reject
    })
    return dfd
  }

  try { // CommonJS compliance
    module.exports = Promise
  } catch(e) {}

  return Promise
})()

最后提一下值穿透:

var promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('haha')
  }, 1000)
})
promise
  .then('hehe')
  .then(console.log)

最終打印 haha 而不是 hehe

Generator:

說(shuō)Generator之前,還是談?wù)処terator 遍歷器比較好

Symbol是一個(gè)特殊的數(shù)據(jù)類型,和number string等并列

console.log(Array.prototype.slice)  // ? slice() { [native code] }
console.log(Array.prototype[Symbol.iterator])  
// ? values() { [native code] }

我們獲取Array.prototype[Symbol.iterator]可以得到一個(gè)函數(shù),只不過(guò)這里的[Symbol.iterator]是Symbol數(shù)據(jù)類型,不是字符串。但是沒(méi)關(guān)系,Symbol數(shù)據(jù)類型也可以作為對(duì)象屬性的key

var obj = {}
obj.a = 100
obj[Symbol.iterator] = 200
console.log(obj)  // {a: 100, Symbol(Symbol.iterator): 200}

只需要知道[Symbol.iterator]是一個(gè)特殊的數(shù)據(jù)類型Symbol類型,但是也可以像number string類型一樣,作為對(duì)象的屬性key來(lái)使用

原生具有[Symbol.iterator]屬性數(shù)據(jù)類型有:數(shù)組、某些類似數(shù)組的對(duì)象(如arguments、NodeList)、Set和Map

原生具有[Symbol.iterator]屬性數(shù)據(jù)類型有一個(gè)特點(diǎn),就是可以使用for...of來(lái)取值

var item
for (item of [100, 200, 300]) {
    console.log(item)
}
// 打印出:100 200 300 
// 注意,這里每次獲取的 item 是數(shù)組的 value,而不是 index ,這一點(diǎn)和 傳統(tǒng) for 循環(huán)以及 for...in 完全不一樣

而具有[Symbol.iterator]屬性的對(duì)象,都可以一鍵生成一個(gè)Iterator對(duì)象

const arr = [100, 200, 300]
const iterator = arr[Symbol.iterator]()  // 通過(guò)執(zhí)行 [Symbol.iterator] 的屬性值(函數(shù))來(lái)返回一個(gè) iterator 對(duì)象

現(xiàn)在生成了iterator,那么該如何使用它呢 ———— 有兩種方式:next和for...of

console.log(iterator.next())  // { value: 100, done: false }
console.log(iterator.next())  // { value: 200, done: false }
console.log(iterator.next())  // { value: 300, done: false }
console.log(iterator.next())  // { value: undefined, done: true }

iterator對(duì)象可以通過(guò)next()方法逐步獲取每個(gè)元素的值,以{ value: ..., done: ... }形式返回,value就是值,done表示是否到已經(jīng)獲取完成

再說(shuō)第二種,for...of

let i
for (i of iterator) {
    console.log(i)
}
// 打?。?00 200 300

上面使用for...of遍歷iterator對(duì)象,可以直接將其值獲取出來(lái)。這里的“值”就對(duì)應(yīng)著上面next()返回的結(jié)果的value屬性

Generator返回的也是Iterator對(duì)象,因此才會(huì)有next(),也可以通過(guò)for...of來(lái)遍歷

正是因?yàn)镚enerator返回的是Iterator對(duì)象,所以我先講生成器對(duì)象

先來(lái)一段最基礎(chǔ)的Generator代碼

function* Hello() {
    yield 100
    yield (function () {return 200})()
    return 300
}

var h = Hello()
console.log(typeof h)  // object

console.log(h.next())  // { value: 100, done: false }
console.log(h.next())  // { value: 200, done: false }
console.log(h.next())  // { value: 300, done: true }
console.log(h.next())  // { value: undefined, done: true }
定義Generator時(shí),需要使用function*,其他的和定義函數(shù)一樣。內(nèi)部使用yield,至于yield的用處以后再說(shuō)

執(zhí)行var h = Hello()生成一個(gè)Generator對(duì)象,經(jīng)驗(yàn)驗(yàn)證typeof h發(fā)現(xiàn)不是普通的函數(shù)

執(zhí)行Hello()之后,Hello內(nèi)部的代碼不會(huì)立即執(zhí)行,而是出于一個(gè)暫停狀態(tài)

執(zhí)行第一個(gè)h.next()時(shí),會(huì)激活剛才的暫停狀態(tài),開(kāi)始執(zhí)行Hello內(nèi)部的語(yǔ)句,但是,直到遇到y(tǒng)ield語(yǔ)句。一旦遇到y(tǒng)ield語(yǔ)句時(shí),它就會(huì)將yield后面的表達(dá)式執(zhí)行,并返回執(zhí)行的結(jié)果,然后又立即進(jìn)入暫停狀態(tài)。

因此第一個(gè)console.log(h.next())打印出來(lái)的是{ value: 100, done: false },value是第一個(gè)yield返回的值,done: false表示目前處于暫停狀態(tài),尚未執(zhí)行結(jié)束,還可以再繼續(xù)往下執(zhí)行。

執(zhí)行第二個(gè)h.next()和第一個(gè)一樣,不在贅述。此時(shí)會(huì)執(zhí)行完第二個(gè)yield后面的表達(dá)式并返回結(jié)果,然后再次進(jìn)入暫停狀態(tài)

執(zhí)行第三個(gè)h.next()時(shí),程序會(huì)打破暫停狀態(tài),繼續(xù)往下執(zhí)行,但是遇到的不是yield而是return。這就預(yù)示著,即將執(zhí)行結(jié)束了。因此最后返回的是{ value: 300, done: true },done: true表示執(zhí)行結(jié)束,無(wú)法再繼續(xù)往下執(zhí)行了。

再去執(zhí)行第四次h.next()時(shí),就只能得到{ value: undefined, done: true },因?yàn)橐呀?jīng)結(jié)束,沒(méi)有返回值了

需要明白以下幾點(diǎn):

Generator不是函數(shù),不是函數(shù),不是函數(shù)
Hello()不會(huì)立即出發(fā)執(zhí)行,而是一上來(lái)就暫停

每次h.next()都會(huì)打破暫停狀態(tài)去執(zhí)行,直到遇到下一個(gè)yield或者return

遇到y(tǒng)ield時(shí),會(huì)執(zhí)行yeild后面的表達(dá)式,并返回執(zhí)行之后的值,然后再次進(jìn)入暫停狀態(tài),此時(shí)done: false。

遇到return時(shí),會(huì)返回值,執(zhí)行結(jié)束,即done: true

每次h.next()的返回值永遠(yuǎn)都是{value: ... , done: ...}的形式

第一個(gè)next()無(wú)需傳入?yún)?shù),它總是啟動(dòng)一個(gè)生成器,并運(yùn)行到第一個(gè)yield處,不過(guò),第二個(gè)next(..)調(diào)用第一個(gè)暫停的yield表達(dá)式,第三個(gè)next(..)調(diào)用第二個(gè)暫停的yield表達(dá)式,最后多出了一個(gè)next(),有return來(lái)回答它

生成器消息是雙向傳遞的,yield(...)作為一個(gè)表達(dá)式可以發(fā)出消息響應(yīng)next.value(),next(...)也可以向暫停的yield表達(dá)式發(fā)送值,可以看下面這個(gè)demo

function *(){
    var y = x * (yield "hello")
    return y
}
var it = foo(6) //將6傳給x
var res = it.next() //啟動(dòng)生成器
res.value() //hello
res = it.next(7) 向等待的yield傳入7
res.value()//42
function* G() {
    const a = yield 100
    console.log('a', a)  // a aaa
    const b = yield 200
    console.log('b', b)  // b bbb
    const c = yield 300
    console.log('c', c)  // c ccc
}
const g = G()
g.next()    // value: 100, done: false
g.next('aaa') // value: 200, done: false
g.next('bbb') // value: 300, done: false
g.next('ccc') // value: undefined, done: true
  • 執(zhí)行第一個(gè)g.next()時(shí),為傳遞任何參數(shù),返回的{value: 100, done: false},這個(gè)應(yīng)該沒(méi)有疑問(wèn)
  • 執(zhí)行第二個(gè)g.next('aaa')時(shí),傳遞的參數(shù)是'aaa',這個(gè)'aaa'就會(huì)被賦值到G內(nèi)部的a標(biāo)量中,然后執(zhí)行console.log('a', a)打印出來(lái),最后返回{value: 200, done: false}
  • 執(zhí)行第三個(gè)、第四個(gè)時(shí),道理都是完全一樣的,大家自己捋一捋

有一個(gè)要點(diǎn)需要注意,就g.next('aaa')是將'aaa'傳遞給上一個(gè)已經(jīng)執(zhí)行完了的yield語(yǔ)句前面的變量,而不是即將執(zhí)行的yield前面的變量。這句話要能看明白

function* fibonacci() {
    let [prev, curr] = [0, 1]
    for (;;) {
        [prev, curr] = [curr, prev + curr]
        // 將中間值通過(guò) yield 返回,并且保留函數(shù)執(zhí)行的狀態(tài),因此可以非常簡(jiǎn)單的實(shí)現(xiàn) fibonacci
        yield curr
    }
}
for (let n of fibonacci()) {
    if (n > 1000) {
        break
    }
    console.log(n)
}

如果有兩個(gè)Generator,想要在第一個(gè)中包含第二個(gè)

function* G1() {
    yield 'a'
    yield* G2()  // 使用 yield* 執(zhí)行 G2()
    yield 'b'
}
function* G2() {
    yield 'x'
    yield 'y'
}
for (let item of G1()) {
    console.log(item)

yield后面會(huì)接一個(gè)普通的 JS 對(duì)象,而yield后面會(huì)接一個(gè)Generator,而且會(huì)把它其中的yield按照規(guī)則來(lái)一步一步執(zhí)行。如果有多個(gè)Generator串聯(lián)使用的話(例如Koa源碼中),用yield來(lái)操作非常方便

Thunk 函數(shù):

往往是將參數(shù)放到一個(gè)臨時(shí)函數(shù)之中,再將這個(gè)臨時(shí)函數(shù)傳入函數(shù)體。這個(gè)臨時(shí)函數(shù)就叫做 Thunk 函數(shù)

function f(m){
  return m * 2;     
}

f(x + 5);

// 等同于

var thunk = function () {
  return x + 5;
};

function f(thunk){
  return thunk() * 2;
}
const thunk = function (fileName, codeType) {
    // 返回一個(gè)只接受 callback 參數(shù)的函數(shù)
    return function (callback) {
        fs.readFile(fileName, codeType, callback)
    }
}
const readFileThunk = thunk('data1.json', 'utf-8')
readFileThunk((err, data) => {
    // 獲取文件內(nèi)容
})

執(zhí)行const readFileThunk = thunk('data1.json', 'utf-8')返回的其實(shí)是一個(gè)函數(shù)
readFileThunk這個(gè)函數(shù),只接受一個(gè)參數(shù),而且這個(gè)參數(shù)是一個(gè)callback函數(shù)

就上上面的代碼,我們經(jīng)過(guò)對(duì)傳統(tǒng)的異步操作函數(shù)進(jìn)行封裝,得到一個(gè)只有一個(gè)參數(shù)的函數(shù),而且這個(gè)參數(shù)是一個(gè)callback函數(shù),那這就是一個(gè)thunk函數(shù)。就像上面代碼中readFileThunk一樣

在Genertor中使用thunk函數(shù)

const readFileThunk = thunkify(fs.readFile)
const gen = function* () {
    const r1 = yield readFileThunk('data1.json')
    console.log(r1)
    const r2 = yield readFileThunk('data2.json')
    console.log(r2)
}

挨個(gè)讀取兩個(gè)文件的內(nèi)容

復(fù)制代碼
const g = gen()

// 試著打印 g.next() 這里一定要明白 value 是一個(gè) thunk函數(shù) ,否則下面的代碼你都看不懂
// console.log( g.next() )  // g.next() 返回 {{ value: thunk函數(shù), done: false }} 

// 下一行中,g.next().value 是一個(gè) thunk 函數(shù),它需要一個(gè) callback 函數(shù)作為參數(shù)傳遞進(jìn)去
g.next().value((err, data1) => {
    // 這里的 data1 獲取的就是第一個(gè)文件的內(nèi)容。下一行中,g.next(data1) 可以將數(shù)據(jù)傳遞給上面的 r1 變量,此前已經(jīng)講過(guò)這種參數(shù)傳遞的形式
    // 下一行中,g.next(data1).value 又是一個(gè) thunk 函數(shù),它又需要一個(gè) callback 函數(shù)作為參數(shù)傳遞進(jìn)去
    g.next(data1).value((err, data2) => {
        // 這里的 data2 獲取的是第二個(gè)文件的內(nèi)容,通過(guò) g.next(data2) 將數(shù)據(jù)傳遞個(gè)上面的 r2 變量
        g.next(data2)
    })
})

自驅(qū)動(dòng)流程:

// 自動(dòng)流程管理的函數(shù)
function run(generator) {
    const g = generator()
    function next(err, data) {
        const result = g.next(data)  // 返回 { value: thunk函數(shù), done: ... }
        if (result.done) {
            // result.done 表示是否結(jié)束,如果結(jié)束了那就 return 作罷
            return
        }
        result.value(next)  // result.value 是一個(gè) thunk 函數(shù),需要一個(gè) callback 函數(shù)作為參數(shù),而 next 就是一個(gè) callback 形式的函數(shù)
    }
    next() // 手動(dòng)執(zhí)行以啟動(dòng)第一次 next
}

// 定義 Generator
const readFileThunk = thunkify(fs.readFile)
const gen = function* () {
    const r1 = yield readFileThunk('data1.json')
    console.log(r1.toString())
    const r2 = yield readFileThunk('data2.json')
    console.log(r2.toString())
}

// 啟動(dòng)執(zhí)行
run(gen)

其實(shí)這段代碼和上面的手動(dòng)編寫讀取兩個(gè)文件內(nèi)容的代碼,原理上是一模一樣的,只不過(guò)這里把流程驅(qū)動(dòng)給封裝起來(lái)了。我們簡(jiǎn)單分析一下這段代碼

  • 最后一行run(gen)之后,進(jìn)入run函數(shù)內(nèi)部執(zhí)行

  • 先const g = generator()創(chuàng)建Generator實(shí)例,然后定義一個(gè)next方法,并且立即執(zhí)行next()

  • 注意這個(gè)next函數(shù)的參數(shù)是err, data兩個(gè),和我們fs.readFile用到的callback函數(shù)形式完全一樣

  • 第一次執(zhí)行next時(shí),會(huì)執(zhí)行const result = g.next(data),而g.next(data)返回的是{ value: thunk函數(shù), done: ... },value是一個(gè)thunk函數(shù),done表示是否結(jié)束

  • 如果done: true,那就直接return了,否則繼續(xù)進(jìn)行

  • result.value是一個(gè)thunk函數(shù),需要接受一個(gè)callback函數(shù)作為參數(shù)傳遞進(jìn)去,因此正好把next給傳遞進(jìn)去,讓next一直被執(zhí)行下去

koa 中如何應(yīng)用Generator:

oa 是一個(gè) web 框架,處理 http 請(qǐng)求,但是這里我們不去管它如何處理 http 請(qǐng)求,而是直接關(guān)注它使用Genertor的部分————中間件

let info = ''
function* g1() {
    info += '1'  // 拼接 1
    yield* g2()  // 拼接 234
    info += '5'  // 拼接 5
}
function* g2() {
    info += '2'  // 拼接 2
    yield* g3()  // 拼接 3
    info += '4'  // 拼接 4
}
function* g3() {
    info += '3'  // 拼接 3
}

var g = g1()
g.next()
console.log(info)  // 12345

但是如果用 koa 的 中間件 的思路來(lái)做,就需要如下這么寫

app.use(function *(next){
    this.body = '1';
    yield next;
    this.body += '5';
    console.log(this.body);
});
app.use(function *(next){
    this.body += '2';
    yield next;
    this.body += '4';
});
app.use(function *(next){
    this.body += '3';
});

app.use()中傳入的每一個(gè)Generator就是一個(gè) 中間件,中間件按照傳入的順序排列,順序不能亂

每個(gè)中間件內(nèi)部,next表示下一個(gè)中間件。yield next就是先將程序暫停,先去執(zhí)行下一個(gè)中間件,等next被執(zhí)行完之后,再回過(guò)頭來(lái)執(zhí)行當(dāng)前代碼的下一行。因此,koa 的中間件執(zhí)行順序是一種洋蔥圈模型,不過(guò)這里看不懂也沒(méi)問(wèn)題。

每個(gè)中間件內(nèi)部,this可以共享變量。即第一個(gè)中間件改變了this的屬性,在第二個(gè)中間件中可以看到效果

koa 的這種應(yīng)用機(jī)制是如何實(shí)現(xiàn)的

class MyKoa extends Object {
    constructor(props) {
        super(props);

        // 存儲(chǔ)所有的中間件
        this.middlewares = []
    }

    // 注入中間件
    use (generator) {
        this.middlewares.push(generator)
    }

    // 執(zhí)行中間件
    listen () {
        this._run()
    }

    _run () {
        const ctx = this
        const middlewares = ctx.middlewares
        co(function* () {
            let prev = null
            let i = middlewares.length
            //從最后一個(gè)中間件到第一個(gè)中間件的順序開(kāi)始遍歷
            while (i--) {
                // ctx 作為函數(shù)執(zhí)行時(shí)的 this 才能保證多個(gè)中間件中數(shù)據(jù)的共享
                //prev 將前面一個(gè)中間件傳遞給當(dāng)前中間件,才使得中間件里面的 next 指向下一個(gè)中間件
                prev = middlewares[i].call(ctx, prev);
            }
            //執(zhí)行第一個(gè)中間件
            yield prev;
        })
    }
}
var app = new MyKoa();
app.use(function *(next){
    this.body = '1';
    yield next;
    this.body += '5';
    console.log(this.body);  // 12345
});
app.use(function *(next){
    this.body += '2';
    yield next;
    this.body += '4';
});
app.use(function *(next){
    this.body += '3';
});
app.listen();

Promise其實(shí)是利用了callback才能實(shí)現(xiàn)的。而這里,Generator也必須利用callback才能實(shí)現(xiàn),如果yield后面用的是thunk函數(shù),那么thunk函數(shù)需要的就是一個(gè)callback參數(shù)。如果yield后面用的是Promise對(duì)象

因此,Generator離不開(kāi)callback,Promise離不開(kāi)callback,異步也離不開(kāi)callback

co(function* () {
    const r1 = yield readFilePromise('some1.json')
    console.log(r1)  // 打印第 1 個(gè)文件內(nèi)容
    const r2 = yield readFilePromise('some2.json')
    console.log(r2)  // 打印第 2 個(gè)文件內(nèi)容
})

再來(lái)一段async-await的執(zhí)行代碼如下,兩者做一個(gè)比較。

const readFilePromise = Q.denodeify(fs.readFile)

// 定義 async 函數(shù)
const readFileAsync = async function () {
    const f1 = await readFilePromise('data1.json')
    const f2 = await readFilePromise('data2.json')
    console.log('data1.json', f1.toString())
    console.log('data2.json', f2.toString())

    return 'done' // 先忽略,后面會(huì)講到
}
// 執(zhí)行
const result = readFileAsync()

從上面兩端代碼比較看來(lái),async function代替了function*,await代替了yield,其他的再?zèng)]有什么區(qū)別了。哦,還有,使用async-await時(shí)候不用再引用co這種第三方庫(kù)了,直接執(zhí)行即可

使用async-await的不同和好處:

  1. await后面不能再跟thunk函數(shù),而必須跟一個(gè)Promise對(duì)象(因此,Promise才是異步的終極解決方案和未來(lái))。跟其他類型的數(shù)據(jù)也OK,但是會(huì)直接同步執(zhí)行,而不是異步

  2. 執(zhí)行const result = readFileAsync()返回的是個(gè)Promise對(duì)象,而且上面代碼中的return 'done'會(huì)直接被下面的then函數(shù)接收到執(zhí)行const result = readFileAsync()返回的是個(gè)Promise對(duì)象,而且上面代碼中的return 'done'會(huì)直接被下面的then函數(shù)接收到

result.then(data => {
    console.log(data)  // done
})
  1. 從代碼的易讀性來(lái)將,async-await更加易讀簡(jiǎn)介,也更加符合代碼的語(yǔ)意。而且還不用引用第三方庫(kù),也無(wú)需學(xué)習(xí)Generator那一堆東西,使用成本非常低

異步操作代碼的變化:

callback方式:

fs.readFile('some1.json', (err, data) => {
    fs.readFile('some2.json', (err, data) => {
        fs.readFile('some3.json', (err, data) => {
            fs.readFile('some4.json', (err, data) => {

            })
        })
    })
})

Promise方式:

readFilePromise('some1.json').then(data => {
    return readFilePromise('some2.json')
}).then(data => {
    return readFilePromise('some3.json')
}).then(data => {
    return readFilePromise('some4.json')
})

Generator方式:

co(function* () {
    const r1 = yield readFilePromise('some1.json')
    const r2 = yield readFilePromise('some2.json')
    const r3 = yield readFilePromise('some3.json')
    const r4 = yield readFilePromise('some4.json')
})

async-await方式:

const readFileAsync = async function () {
    const f1 = await readFilePromise('data1.json')
    const f2 = await readFilePromise('data2.json')
    const f3 = await readFilePromise('data3.json')
    const f4 = await readFilePromise('data4.json')
}

以下是參考鏈接:

深入理解js異步系列

event loop

阮一峰

史上最易讀懂的 Promise/A+ 完全實(shí)現(xiàn)

Promise 實(shí)現(xiàn)詳解

大白話講解Promise

八段代碼徹底掌握 Promise

深入核心,詳解事件循環(huán)機(jī)制

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容