Javascript語(yǔ)言的執(zhí)行環(huán)境是"單線程"(single thread),所謂"單線程"泞遗,就是指一次只能完成一件任務(wù)惰许。如果有多個(gè)任務(wù),就必須排隊(duì)刹孔,前面一個(gè)任務(wù)完成啡省,再執(zhí)行后面一個(gè)任務(wù),以此類推髓霞。這種模式的好處是實(shí)現(xiàn)起來比較簡(jiǎn)單卦睹,執(zhí)行環(huán)境相對(duì)單純;壞處是只要有一個(gè)任務(wù)耗時(shí)很長(zhǎng)方库,后面的任務(wù)都必須排隊(duì)等著结序,會(huì)拖延整個(gè)程序的執(zhí)行。常見的瀏覽器無(wú)響應(yīng)(假死)纵潦,往往就是因?yàn)槟骋欢蜫avascript代碼長(zhǎng)時(shí)間運(yùn)行(比如死循環(huán))徐鹤,導(dǎo)致整個(gè)頁(yè)面卡在這個(gè)地方,其他任務(wù)無(wú)法執(zhí)行邀层。
為了解決這個(gè)問題返敬,Javascript語(yǔ)言將任務(wù)的執(zhí)行模式分成兩種:同步(Synchronous)和異步(Asynchronous)。"同步模式"就是上一段的模式寥院,后一個(gè)任務(wù)等待前一個(gè)任務(wù)結(jié)束劲赠,然后再執(zhí)行,程序的執(zhí)行順序與任務(wù)的排列順序是一致的秸谢、同步的悯恍;"異步模式"則完全不同商叹,每一個(gè)任務(wù)有一個(gè)或多個(gè)回調(diào)函數(shù)(callback)叉袍,前一個(gè)任務(wù)結(jié)束后荠列,不是執(zhí)行后一個(gè)任務(wù),而是執(zhí)行回調(diào)函數(shù)臭蚁,后一個(gè)任務(wù)則是不等前一個(gè)任務(wù)結(jié)束就執(zhí)行最铁,所以程序的執(zhí)行順序與任務(wù)的排列順序是不一致的讯赏、異步的。
"異步模式"非常重要炭晒。在瀏覽器端待逞,耗時(shí)很長(zhǎng)的操作都應(yīng)該異步執(zhí)行,避免瀏覽器失去響應(yīng)网严,最好的例子就是Ajax操作。在服務(wù)器端嗤无,"異步模式"甚至是唯一的模式震束,因?yàn)閳?zhí)行環(huán)境是單線程的,如果允許同步執(zhí)行所有http請(qǐng)求当犯,服務(wù)器性能會(huì)急劇下降垢村,很快就會(huì)失去響應(yīng)。
在ES6誕生以前嚎卫,異步編程的方式大概有下面四種:回調(diào)函數(shù)嘉栓、事件監(jiān)聽、發(fā)布/訂閱拓诸、Promise對(duì)象侵佃。ES6中,引入了Generator函數(shù)奠支;ES7中馋辈,async更是將異步編程帶入了一個(gè)全新的階段。
Promise對(duì)象
ES6 規(guī)定倍谜,Promise對(duì)象是一個(gè)構(gòu)造函數(shù)迈螟,用來生成Promise實(shí)例。Promise構(gòu)造函數(shù)接受一個(gè)函數(shù)作為參數(shù)尔崔,該函數(shù)的兩個(gè)參數(shù)分別是resolve和reject答毫。它們是兩個(gè)函數(shù),由 JavaScript 引擎提供季春,不用自己部署洗搂。
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 異步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
resolve函數(shù)的作用是,將Promise對(duì)象的狀態(tài)從 pending 變?yōu)?resolved鹤盒,在異步操作成功時(shí)調(diào)用蚕脏,并將異步操作的結(jié)果,作為參數(shù)傳遞出去侦锯;reject函數(shù)的作用是驼鞭,將Promise對(duì)象的狀態(tài)從pending 變?yōu)?rejected,在異步操作失敗時(shí)調(diào)用尺碰,并將異步操作報(bào)出的錯(cuò)誤挣棕,作為參數(shù)傳遞出去译隘。
Promise實(shí)例生成以后,可以用then方法分別指定resolved狀態(tài)和rejected狀態(tài)的回調(diào)函數(shù)洛心。then方法可以接受兩個(gè)回調(diào)函數(shù)作為參數(shù)固耘。第一個(gè)回調(diào)函數(shù)是Promise對(duì)象的狀態(tài)變?yōu)閞esolved時(shí)調(diào)用,第二個(gè)回調(diào)函數(shù)是Promise對(duì)象的狀態(tài)變?yōu)閞ejected時(shí)調(diào)用词身。其中厅目,第二個(gè)函數(shù)是可選的,不一定要提供法严。這兩個(gè)函數(shù)都接受Promise對(duì)象傳出的值作為參數(shù)损敷。
Generator函數(shù)
Generator 函數(shù)是協(xié)程在 ES6 的實(shí)現(xiàn),最大特點(diǎn)就是可以交出函數(shù)的執(zhí)行權(quán)(即暫停執(zhí)行)深啤。
整個(gè) Generator 函數(shù)就是一個(gè)封裝的異步任務(wù)拗馒,或者說是異步任務(wù)的容器。異步操作需要暫停的地方溯街,都用yield語(yǔ)句注明诱桂。Generator 函數(shù)的執(zhí)行方法如下。
function* gen(x) {
var y = yield x + 2;
return y;
}
var g = gen(1);
g.next() // { value: 3, done: false }
g.next() // { value: undefined, done: true }
上面代碼中呈昔,調(diào)用 Generator 函數(shù)挥等,會(huì)返回一個(gè)內(nèi)部指針(即遍歷器)g。這是 Generator 函數(shù)不同于普通函數(shù)的另一個(gè)地方韩肝,即執(zhí)行它不會(huì)返回結(jié)果触菜,返回的是指針對(duì)象。調(diào)用指針g的next方法哀峻,會(huì)移動(dòng)內(nèi)部指針(即執(zhí)行異步任務(wù)的第一段)涡相,指向第一個(gè)遇到的yield語(yǔ)句,上例是執(zhí)行到x + 2為止剩蟀。
換言之催蝗,next方法的作用是分階段執(zhí)行Generator函數(shù)。每次調(diào)用next方法育特,會(huì)返回一個(gè)對(duì)象丙号,表示當(dāng)前階段的信息(value屬性和done屬性)。value屬性是yield語(yǔ)句后面表達(dá)式的值缰冤,表示當(dāng)前階段的值犬缨;done屬性是一個(gè)布爾值,表示 Generator 函數(shù)是否執(zhí)行完畢棉浸,即是否還有下一個(gè)階段怀薛。
由于Generator函數(shù)無(wú)法自動(dòng)執(zhí)行,所以需要用到Thunk 函數(shù)或co模塊來實(shí)現(xiàn)自動(dòng)化執(zhí)行迷郑。
Async函數(shù)
async 函數(shù)就是 Generator 函數(shù)的語(yǔ)法糖枝恋。將 Generator 函數(shù)的星號(hào)(*)替換成async创倔,將yield替換成await,僅此而已焚碌。
async 函數(shù)的實(shí)現(xiàn)原理畦攘,就是將 Generator 函數(shù)和自動(dòng)執(zhí)行器,包裝在一個(gè)函數(shù)里十电。
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function* () { //spawn函數(shù)就是自動(dòng)執(zhí)行器
// ...
});
}
async函數(shù)對(duì) Generator 函數(shù)的改進(jìn)知押,體現(xiàn)在以下四點(diǎn)。
(1)內(nèi)置執(zhí)行器鹃骂。
async函數(shù)自帶執(zhí)行器朗徊。也就是說,async函數(shù)的執(zhí)行偎漫,與普通函數(shù)一模一樣,調(diào)用后就會(huì)自動(dòng)執(zhí)行有缆,輸出最后結(jié)果象踊。這完全不像 Generator 函數(shù),需要調(diào)用next方法棚壁,或者用co模塊杯矩,才能真正執(zhí)行,得到最后結(jié)果袖外。
(2)更好的語(yǔ)義史隆。
async和await,比起星號(hào)和yield曼验,語(yǔ)義更清楚了泌射。async表示函數(shù)里有異步操作,await表示緊跟在后面的表達(dá)式需要等待結(jié)果鬓照。
(3)更廣的適用性熔酷。
co模塊約定,yield命令后面只能是 Thunk 函數(shù)或 Promise 對(duì)象豺裆,而async函數(shù)的await命令后面拒秘,可以是 Promise 對(duì)象和原始類型的值(一般來說是一個(gè) Promise 對(duì)象。如果不是臭猜,會(huì)被轉(zhuǎn)成一個(gè)立即resolve的 Promise 對(duì)象躺酒,類似于同步操作)。
(4)返回值是 Promise蔑歌。
async函數(shù)的返回值是 Promise 對(duì)象羹应,這比 Generator 函數(shù)的返回值是 Iterator 對(duì)象方便多了。你可以用then方法指定下一步的操作丐膝。
進(jìn)一步說量愧,async函數(shù)完全可以看作多個(gè)異步操作钾菊,包裝成的一個(gè) Promise 對(duì)象,而await命令就是內(nèi)部then命令的語(yǔ)法糖偎肃。
async 函數(shù)有多種使用形式煞烫。
// 函數(shù)聲明
async function foo() {}
// 函數(shù)表達(dá)式
const foo = async function () {};
// 對(duì)象的方法
let obj = { async foo() {} };
obj.foo().then(...)
// Class 的方法
class Storage {
constructor() {
this.cachePromise = caches.open('avatars');
}
async getAvatar(name) {
const cache = await this.cachePromise;
return cache.match(`/avatars/${name}.jpg`);
}
}
const storage = new Storage();
storage.getAvatar('jake').then(…);
// 箭頭函數(shù)
const foo = async () => {};
await操作符只能在異步函數(shù) async function 中使用,它使 async 函數(shù)暫停執(zhí)行累颂,等待表達(dá)式中的 Promise解析完成后繼續(xù)執(zhí)行 async 函數(shù)并返回解決結(jié)果滞详,所以async函數(shù)內(nèi)部await后的表達(dá)式是順序執(zhí)行的。多個(gè)await命令后面的異步操作紊馏,如果不存在繼發(fā)關(guān)系料饥,最好讓它們同時(shí)觸發(fā),可以縮短程序的執(zhí)行時(shí)間朱监。
async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));
let results = await Promise.all(promises);
console.log(results);
}