Promise Iterator Generator async-await 都是異步解決方案,Iterator Generator async-await之間有千絲萬縷的聯(lián)系,起初從Iterator開始向后理解,感覺不好理解,于是我覺得倒著理解,從常用的async-await到其背后演變過程。
全文參考阮一峰老師的ES6 入門教程
一、Promise
Promise比較簡單,基本用法如下:
const promise = new Promise((resolve, reject) => {
if(....){
resolve(value)
}else{
reject(error)
}
})
promise.then(() => {
// 成功走這里
}).catch(() => {
// 失敗走這里
}).finallly(() => {
// 無論最后狀態(tài)如何,都會執(zhí)行
})
Promise.resolve()
Promise.reject()
const p = Promise.all([p1, p2, p3]);
p.then(function (posts) {
// ...
}).catch(function(reason){
// ...
});
const p = Promise.race([p1, p2, p3]);
Promise.all()方法用于將多個 Promise 實例,包裝成一個新的 Promise 實例。p1、p2、p3都是 Promise 實例,如果不是,就會先調(diào)用Promise.resolve方法,將參數(shù)轉(zhuǎn)為 Promise 實例,再進一步處理。
p的狀態(tài)由p1、p2、p3決定,分成兩種情況。
(1)只有p1、p2、p3的狀態(tài)都變成fulfilled,p的狀態(tài)才會變成fulfilled,此時p1、p2、p3的返回值組成一個數(shù)組,傳遞給p的回調(diào)函數(shù)。
(2)只要p1、p2、p3之中有一個被rejected,p的狀態(tài)就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調(diào)函數(shù)。但是,如果p1被rejected,而且p1有自己的catch方法,則p不會走catch,此時p1執(zhí)行catch后會變成resolved
Promise.race()則是p1,p2,p3中其中一個狀態(tài)改變,p的狀態(tài)就改變,那個率先改變的 Promise 實例的返回值,就傳遞給p的回調(diào)函數(shù)。
二、async-await
async函數(shù)是Generator 函數(shù)的語法糖。async定義一個異步函數(shù),這個函數(shù)內(nèi)用await來調(diào)用其他異步函數(shù),拿到結(jié)果,再繼續(xù)執(zhí)行下方代碼。
const fs = require('fs');
const readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) return reject(error);
resolve(data);
});
});
};
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
async函數(shù)的返回值是 Promise 對象。async函數(shù)完全可以看作多個異步操作,包裝成的一個 Promise 對象,而await命令就是內(nèi)部then命令的語法糖。
為防止await后的異步方法出錯,可以使用try{}catch{}
的結(jié)構(gòu)。
知道了async函數(shù)是Generator 函數(shù)的語法糖。那么Generator是什么呢?
三、Generator(生成器)
上方的例子用Generator寫是這樣的:
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
一比較就會發(fā)現(xiàn),async函數(shù)就是將 Generator 函數(shù)的星號(*)替換成async,將yield替換成await,僅此而已。
Generator 函數(shù)是一個狀態(tài)機,封裝了多個內(nèi)部狀態(tài)。執(zhí)行 Generator 函數(shù)會返回一個遍歷器對象,Generator 函數(shù)還是一個遍歷器生成函數(shù)。返回的遍歷器對象,可以依次遍歷 Generator 函數(shù)內(nèi)部的每一個狀態(tài)。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
調(diào)用 Generator 函數(shù)后,該函數(shù)并不執(zhí)行,返回的也不是函數(shù)運行結(jié)果,而是一個指向內(nèi)部狀態(tài)的指針對象,也就是遍歷器對象(Iterator Object)。yield表達式是暫停標(biāo)志。必須調(diào)用遍歷器對象的next方法,使得指針移向下一個狀態(tài)。next方法可以帶一個參數(shù),該參數(shù)就會被當(dāng)作上一個yield表達式的返回值。
形式上,Generator 函數(shù)是一個普通函數(shù),但是有兩個特征。一是,function關(guān)鍵字與函數(shù)名之間有一個星號;二是,函數(shù)體內(nèi)部使用yield表達式,定義不同的內(nèi)部狀態(tài)(yield在英語里的意思就是“產(chǎn)出”)。
與 Iterator 接口的關(guān)系
任意一個對象的Symbol.iterator方法,等于該對象的遍歷器生成函數(shù),調(diào)用該函數(shù)會返回該對象的一個遍歷器對象。
由于 Generator 函數(shù)就是遍歷器生成函數(shù),因此可以把 Generator 賦值給對象的Symbol.iterator屬性,從而使得該對象具有 Iterator 接口。
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]
Generator 函數(shù)執(zhí)行后,返回一個遍歷器對象。該對象本身也具有Symbol.iterator屬性,執(zhí)行后返回自身.
function* gen(){
// some code
}
var g = gen();
g[Symbol.iterator]() === g
// true
async函數(shù)對 Generator 函數(shù)的改進。
- 內(nèi)置執(zhí)行器。async函數(shù)的執(zhí)行,與普通函數(shù)一樣,但Generator 函數(shù),需要調(diào)用next方法,或者用co模塊,才能真正執(zhí)行。
- 更好的語義。async和await,比起星號和yield,語義更清楚了。async表示函數(shù)里有異步操作,await表示緊跟在后面的表達式需要等待結(jié)果。
- 更廣的適用性。co模塊約定,yield命令后面只能是 Thunk 函數(shù)或 Promise 對象,而async函數(shù)的await命令后面,可以是 Promise 對象和原始類型的值。
- 返回值是 Promise。async函數(shù)的返回值是 Promise 對象, Generator 函數(shù)的返回值是 Iterator。async函數(shù)完全可以看作多個異步操作,包裝成的一個 Promise 對象,而await命令就是內(nèi)部then命令的語法糖。
for...of 循環(huán)
for...of循環(huán)可以自動遍歷 Generator 函數(shù)運行時生成的Iterator對象,且此時不再需要調(diào)用next方法。return語句返回的值,不包括在for...of循環(huán)之中。
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
前面提到,generator函數(shù)返回的是一個遍歷器對象(Iterator Object),那么Iterator又是什么?
四、Iterator(遍歷器)
概念
遍歷器(Iterator)是一種接口,為各種不同的數(shù)據(jù)結(jié)構(gòu)提供統(tǒng)一的訪問機制。任何數(shù)據(jù)結(jié)構(gòu)只要部署 Iterator 接口,就可以完成遍歷操作(即依次處理該數(shù)據(jù)結(jié)構(gòu)的所有成員)。
Iterator 的作用有三個:一是為各種數(shù)據(jù)結(jié)構(gòu),提供一個統(tǒng)一的、簡便的訪問接口;二是使得數(shù)據(jù)結(jié)構(gòu)的成員能夠按某種次序排列;三是 ES6 創(chuàng)造了一種新的遍歷命令for...of循環(huán),Iterator 接口主要供for...of消費。
var it = makeIterator(['a', 'b']);
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}
上面代碼定義了一個makeIterator函數(shù),它是一個遍歷器生成函數(shù),作用就是返回一個遍歷器對象。它的執(zhí)行過程是:
- 創(chuàng)建一個指針對象指向當(dāng)前數(shù)據(jù)結(jié)構(gòu)的其實位置。(遍歷器對象本質(zhì)上,就是一個指針對象。)
- 調(diào)用next()將指針指向第一個成員。繼續(xù)不斷調(diào)用next(),指向后續(xù)成員,知道指向最后一個成員。
默認 Iterator 接口
ES6 規(guī)定,默認的 Iterator 接口部署在數(shù)據(jù)結(jié)構(gòu)的Symbol.iterator屬性。Symbol.iterator屬性本身是一個函數(shù),就是當(dāng)前數(shù)據(jù)結(jié)構(gòu)默認的遍歷器生成函數(shù)。
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
調(diào)用 Iterator 接口的場合
- 解構(gòu)賦值。
let [first, ...rest] = ['a', 'b', 'c'];// first='a'; rest=['b','c'];
- 擴展運算符。
var str = 'hello'; [...str] // ['h','e','l','l','o']
- yield*。 generator函數(shù)中
yield * [1, 2, 3]
等
for...of循環(huán)
ES6 借鑒 C++、Java、C# 和 Python 語言,引入了for...of循環(huán),作為遍歷所有數(shù)據(jù)結(jié)構(gòu)的統(tǒng)一的方法。
一個數(shù)據(jù)結(jié)構(gòu)只要部署了Symbol.iterator屬性,就被視為具有 iterator 接口,就可以用for...of循環(huán)遍歷它的成員。也就是說,for...of循環(huán)內(nèi)部調(diào)用的是數(shù)據(jù)結(jié)構(gòu)的Symbol.iterator方法。
let arr = [3, 5, 7];
arr.foo = 'hello';
for (let i in arr) {
console.log(i); // "0", "1", "2", "foo"
}
for (let i of arr) {
console.log(i); // "3", "5", "7"
}
上面代碼表明,for...in循環(huán)讀取鍵名,for...of循環(huán)讀取鍵值。