迭代器(Iterator)和生成器(Generator)
迭代器(Iterator)
1.什么是迭代器Iterator
- 迭代器是一種特殊對(duì)象,它具有一些專門為迭代過程設(shè)計(jì)的專有接口,所有的迭代器對(duì)象都有一個(gè)next()方法,每次調(diào)用都返回一個(gè)結(jié)果對(duì)象。結(jié)果對(duì)象有兩個(gè)屬性:一個(gè)是value,表示下一個(gè)將要返回的值;另一個(gè)是done,它是一個(gè)布爾類型的值,當(dāng)沒有更多可返回?cái)?shù)據(jù)時(shí)返回true。迭代器還會(huì)保存一個(gè)內(nèi)部指針,用來指向當(dāng)前集合中值的位置,每調(diào)用一次next()方法,都會(huì)返回下一個(gè)可用的值。
- 如果在最后一個(gè)值返回后再調(diào)用next()方法,那么返回的對(duì)象中屬性done的值為true,屬性value則包含迭代器最終返回的值,這個(gè)返回值不是數(shù)據(jù)集的一部分,它與函數(shù)的返回值類似,是函數(shù)調(diào)用過程中最后一次給調(diào)用者傳遞信息的方法,如果沒有相關(guān)數(shù)據(jù)則返回undefined
2.集合對(duì)象迭代器
在ES6中,所有的集合對(duì)象(數(shù)組、Set集合及Map集合)和字符串都是可迭代對(duì)象,這些對(duì)象中都有默認(rèn)的迭代器。ES6中新加入的特性for-of循環(huán)需要用到可迭代對(duì)象的這些功能:
- entries() 返回鍵值對(duì)迭代器: Map默認(rèn)迭代器
- keys() 返回鍵名迭代器
- values() 返回集合值迭代器: Array、Set默認(rèn)迭代器
const map = new Map([['name', 'Jony'], ['age', 25]]);
const mapIterator = map.entries();
mapIterator.next(); // " {done: false,value: ['name', 'Jony']}
mapIterator.next(); // {done: false,value: ['age', '25']}
mapIterator.next(); // {done: true,value: undefined}
3.什么是生成器Generator
生成器是一種返回迭代器的函數(shù),通過function關(guān)鍵字后的星號(hào)(*)來表示,函數(shù)中會(huì)用到新的關(guān)鍵字yield。星號(hào)可以緊挨著function關(guān)鍵字,也可以在中間添加一個(gè)空格
// 生成器
function *createIterator() {
yield 1;
yield 2;
yield 3;
}
// 生成器能像正規(guī)函數(shù)那樣被調(diào)用,但會(huì)返回一個(gè)迭代器
let iterator = createIterator();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3
- 在這個(gè)示例中,
createlterator()前的星號(hào)表明它是一個(gè)生成器;yield關(guān)鍵字也是ES6的新特性,可以通過它來指定調(diào)用迭代器的next()方法時(shí)的返回值及返回順序。生成迭代器后,連續(xù)3次調(diào)用它的next()方法返回3個(gè)不同的值,分別是1、2和3。生成器的調(diào)用過程與其他函數(shù)一樣,最終返回的是創(chuàng)建好的迭代器 - 生成器函數(shù)最有趣的部分是,每當(dāng)執(zhí)行完一條
yield語句后函數(shù)就會(huì)自動(dòng)停止執(zhí)行。舉個(gè)例子,在上面這段代碼中,執(zhí)行完語句yield 1之后,函數(shù)便不再執(zhí)行其他任何語句,直到再次調(diào)用迭代器的next()方法才會(huì)繼續(xù)執(zhí)行yield2語句。生成器函數(shù)的這種中止函數(shù)執(zhí)行的能力有很多有趣的應(yīng)用 - 使用yield關(guān)鍵字可以返回任何值或表達(dá)式,所以可以通過生成器函數(shù)批量地給迭代器添加元素。例如,可以在循環(huán)中使用
yield關(guān)鍵字
function *createIterator(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
}
let iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"http:// 之后的所有調(diào)用
console.log(iterator.next()); // "{ value: undefined, done: true }"
- 在此示例中,給生成器函數(shù)
createlterator()傳入一個(gè)items數(shù)組,而在函數(shù)內(nèi)部,for循環(huán)不斷從數(shù)組中生成新的元素放入迭代器中,每遇到一個(gè)yield語句循環(huán)都會(huì)停止;每次調(diào)用迭代器的next()方法,循環(huán)會(huì)繼續(xù)運(yùn)行并執(zhí)行下一條yield語句
【使用限制】
yield關(guān)鍵字只可在生成器內(nèi)部使用,在其他地方使用會(huì)導(dǎo)致程序拋出錯(cuò)誤
function *createIterator(items) {
items.forEach(function(item) {
// 語法錯(cuò)誤
yield item + 1;
});
}
- 從字面上看,
yield關(guān)鍵字確實(shí)在createlterator()函數(shù)內(nèi)部,但是它與return關(guān)鍵字一樣,二者都不能穿透函數(shù)邊界。嵌套函數(shù)中的return語句不能用作外部函數(shù)的返回語句,而此處嵌套函數(shù)中的yield語句會(huì)導(dǎo)致程序拋出語法錯(cuò)誤
【生成器對(duì)象的方法】
由于生成器本身就是函數(shù),因而可以將它們添加到對(duì)象中。例如,在ES5風(fēng)格的對(duì)象字面量中,可以通過函數(shù)表達(dá)式來創(chuàng)建生成器
var o = {
createIterator: function *(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
}
};
let iterator = o.createIterator([1, 2, 3]);
也可以用
ES6的函數(shù)方法的簡(jiǎn)寫方式來創(chuàng)建生成器,只需在函數(shù)名前添加一個(gè)星號(hào)(*)
var o = {
*createIterator(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
}
};
let iterator = o.createIterator([1, 2, 3]);
4.創(chuàng)建可迭代對(duì)象
默認(rèn)情況下,開發(fā)者定義的對(duì)象都是不可迭代對(duì)象,但如果給Symbol.iterator屬性添加一個(gè)生成器,則可將其變?yōu)榭傻鷮?duì)象
let collection = {
items: [],
*[Symbol.iterator]() {
for (let item of items) {
yield item;
}
}
};
collection.items.push(1);
collection.items.push(2);
collection.items.push(3);
for (let x of collection) {
console.log(x);
}
5.訪問默認(rèn)的迭代器
let values = [1, 2, 3];
let iterator = values[Symbol.iterator]();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }" //
6.字符串迭代器
const message = "A ?? B";
const message = "A 中 B";
for (let i = 0; i < message.length; i++) {
console.log(message[i]); // A ()??()B // A () () () () B
}
for (let item of message) {
console.log(item);// A () ?? () B
}
- 由于雙字節(jié)字符被視作兩個(gè)獨(dú)立的編碼單元,從而最終在A與B之間打印出4個(gè)空行
- 所幸,ES6的目標(biāo)是全面支持Unicode,并且我們可以通過改變字符串的默認(rèn)迭代器來解決這個(gè)問題,使其操作字符而不是編碼單元?,F(xiàn)在,修改前一個(gè)示例中字符串的默認(rèn)迭代器,讓for-of循環(huán)輸出正確的內(nèi)容
7.NodeList迭代器
const divs = document.getElementsByTagName('div');
for (let div of divs) {
console.log(div.id);
}
8.展開運(yùn)算符和非數(shù)組可迭代對(duì)象
通過展開運(yùn)算符(...)可以把Set集合轉(zhuǎn)換成一個(gè)數(shù)組
let set = new Set([1, 2, 3, 3, 3, 4, 5]),
array = [...set];
console.log(array); // [1,2,3,4,5]
- 這段代碼中的展開運(yùn)算符把
Set集合的所有值填充到了一個(gè)數(shù)組字面量里,它可以操作所有可迭代對(duì)象,并根據(jù)默認(rèn)迭代器來選取要引用的值,從迭代器讀取所有值。然后按照返回順序?qū)⑺鼈円来尾迦氲綌?shù)組中。Set集合是一個(gè)可迭代對(duì)象,展開運(yùn)算符也可以用于其他可迭代對(duì)象
let map = new Map([ ["name", "huochai"], ["age", 25]]),
array = [...map];
console.log(array); // [ ["name", "huochai"], ["age", 25]]
- 展開運(yùn)算符把
Map集合轉(zhuǎn)換成包含多個(gè)數(shù)組的數(shù)組,Map集合的默認(rèn)迭代器返回的是多組鍵值對(duì),所以結(jié)果數(shù)組與執(zhí)行new Map()時(shí)傳入的數(shù)組看起來一樣 - 在數(shù)組字面量中可以多次使用展開運(yùn)算符,將可迭代對(duì)象中的多個(gè)元素依次插入新數(shù)組中,替換原先展開運(yùn)算符所在的位置
let smallNumbers = [1, 2, 3],
bigNumbers = [100, 101, 102],
allNumbers = [0, ...smallNumbers, ...bigNumbers];
console.log(allNumbers.length); // 7
console.log(allNumbers); // [0, 1, 2, 3, 100, 101, 102]
- 創(chuàng)建一個(gè)變量
allNumbers,用展開運(yùn)算符將smallNumbers和bigNumbers里的值依次添加到allNumbers中。首先存入0,然后存入small中的值,最后存入bigNumbers中的值。當(dāng)然,原始數(shù)組中的值只是被復(fù)制到allNumbers中,它們本身并未改變 - 由于展開運(yùn)算符可以作用于任意可迭代對(duì)象,因此如果想將可迭代對(duì)象轉(zhuǎn)換為數(shù)組,這是最簡(jiǎn)單的方法。既可以將字符串中的每一個(gè)字符(不是編碼單元)存入新數(shù)組中,也可以將瀏覽器中
NodeList對(duì)象中的每一個(gè)節(jié)點(diǎn)存入新的數(shù)組中
9.例子
例子A
let state = function*(){
while(1){
yield 'A';
yield 'B';
yield 'C';
}
}
let status = state();
console.log(status.next().value);//'A'
console.log(status.next().value);//'B'
console.log(status.next().value);//'C'
console.log(status.next().value);//'A'
console.log(status.next().value);//'B'
例子B
function *createIterator() {
console.log('begin');
let a = yield 1;
console.log('a', a);
let b = yield a + 2;
console.log('b', b);
yield b + 3;
console.log('finish');
}
let iterator = createIterator();
iterator.next(0); // return {value: 1, done: false}
// begin
iterator.next(2); // return {value: 4, done: false}
// a 2
iterator.next(4);// return {value: 6, done: false}
// b 4
iterator.next(6);// return {value: undefined, done: true}
// finish
- 通過上面高級(jí)迭代器的代碼和輸出,我們可以看出迭代器的運(yùn)行原理。生成器Generator的代碼根據(jù)
yield關(guān)鍵字被拆分成了多個(gè)函數(shù),類似于split('yield')的效果。在運(yùn)行了iterator.next(0);之后createIterator執(zhí)行了console.log('begin'); yield 1;。至于let a = yield 1;,相當(dāng)于return 1和let a = next(),所以a的值是iterator.next(2);的參數(shù)2。
例子C
function *createIterator() {
let first = yield 1;
let second = yield first + 2; // 4 + 2
yield second + 3; // 5 + 3
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next(4)); // "{ value: 6, done: false }"
console.log(iterator.next(5)); // "{ value: 8, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
- 第一次調(diào)用
next()方法時(shí)無論傳入什么參數(shù)都會(huì)被丟棄。由于傳給next()方法的參數(shù)會(huì)替代上一次yield的返回值,而在第一次調(diào)用next()方法前不會(huì)執(zhí)行任何yield語句,因此在第一次調(diào)用next()方法時(shí)傳遞參數(shù)是毫無意義的 - 第二次調(diào)用
next()方法傳入數(shù)值4作為參數(shù),它最后被賦值給生成器函數(shù)內(nèi)部的變量first。在一個(gè)含參yield語句中,表達(dá)式右側(cè)等價(jià)于第一次調(diào)用next()方法后的下一個(gè)返回值,表達(dá)式左側(cè)等價(jià)于第二次調(diào)用next()方法后,在函數(shù)繼續(xù)執(zhí)行前得到的返回值。第二次調(diào)用next()方法傳入的值為4,它會(huì)被賦值給變量first,函數(shù)則繼續(xù)執(zhí)行。第二條yield語句在第一次yield的結(jié)果上加了2,最終的返回值為6 - 第三次調(diào)用
next()方法時(shí),傳入數(shù)值5,這個(gè)值被賦值給second,最后用于第三條yield語句并最終返回?cái)?shù)值8
10.在迭代器中拋出錯(cuò)誤
除了給迭代器傳遞數(shù)據(jù)外,還可以給它傳遞錯(cuò)誤條件。通過
throw()方法,當(dāng)?shù)骰謴?fù)執(zhí)行時(shí)可令其拋出一個(gè)錯(cuò)誤。這種主動(dòng)拋出錯(cuò)誤的能力對(duì)于異步編程而言至關(guān)重要,也能提供模擬結(jié)束函數(shù)執(zhí)行的兩種方法(返回值或拋出錯(cuò)誤),從而增強(qiáng)生成器內(nèi)部的編程彈性。將錯(cuò)誤對(duì)象傳給throw()方法后,在迭代器繼續(xù)執(zhí)行時(shí)其會(huì)被拋出
function *createIterator() {
let first = yield 1;
let second = yield first + 2; // yield 4 + 2 ,然后拋出錯(cuò)誤
yield second + 3; // 永不會(huì)被執(zhí)行
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next(4)); // "{ value: 6, done: false }"
console.log(iterator.throw(new Error("Boom"))); // 從生成器中拋出了錯(cuò)誤
console.log(iterator.next(5)); // "{ value: undefined, done: true }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
捕獲錯(cuò)誤
function *createIterator() {
let first = yield 1;
let second;
try {
second = yield first + 2; // yield 4 + 2 ,然后拋出錯(cuò)誤
} catch (ex) {
second = 6; // 當(dāng)出錯(cuò)時(shí),給變量另外賦值
}
yield second + 3;
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next(4)); // "{ value: 6, done: false }"
console.log(iterator.throw(new Error("Boom"))); // "{ value: 9, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
- 在此示例中,
try-catch代碼塊包裹著第二條yield語句。盡管這條語句本身沒有錯(cuò)誤,但在給變量second賦值前還是會(huì)主動(dòng)拋出錯(cuò)誤,catch代碼塊捕獲錯(cuò)誤后將second變量賦值為6,下一條yield語句繼續(xù)執(zhí)行后返回9 - 這里有一個(gè)有趣的現(xiàn)象調(diào)用
throw()方法后也會(huì)像調(diào)用next()方法一樣返回一個(gè)結(jié)果對(duì)象。由于在生成器內(nèi)部捕獲了這個(gè)錯(cuò)誤,因而會(huì)繼續(xù)執(zhí)行下一條yield語句,最終返回?cái)?shù)值9 - 如此一來,
next()和throw()就像是迭代器的兩條指令,調(diào)用next()方法命令迭代器繼續(xù)執(zhí)行(可能提供一個(gè)值),調(diào)用throw()方法也會(huì)命令迭代器繼續(xù)執(zhí)行,但同時(shí)也拋出一個(gè)錯(cuò)誤,在此之后的執(zhí)行過程取決于生成器內(nèi)部的代碼 - 在迭代器內(nèi)部,如果使用了
yield語句,則可以通過next()方法和throw()方法控制執(zhí)行過程,當(dāng)然,也可以使用return語句返回一些與普通函數(shù)返回語句不太一樣的內(nèi)容
11.生成器返回語句
function *createIterator() {
yield 1;
return;
yield 2;
yield 3;
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
function *createIterator() {
yield 1;
return 42;
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 42, done: true }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
12.委托生成器
function *createNumberIterator() {
yield 1;
yield 2;
}
function *createColorIterator() {
yield "red";
yield "green";
}
function *createCombinedIterator() {
yield *createNumberIterator();
yield *createColorIterator();
yield true;
}
var iterator = createCombinedIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: "red", done: false }"
console.log(iterator.next()); // "{ value: "green", done: false }"
console.log(iterator.next()); // "{ value: true, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
function *createNumberIterator() {
yield 1;
yield 2;
return 3;
}
function *createRepeatingIterator(count) {
for (let i=0; i < count; i++) {
yield "repeat";
}
}
function *createCombinedIterator() {
let result = yield *createNumberIterator();
yield *createRepeatingIterator(result);
}
var iterator = createCombinedIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
- 在生成器
createCombinedlterator()中,執(zhí)行過程先被委托給了生成器createNumberlterator(),返回值會(huì)被賦值給變量result,執(zhí)行到return 3時(shí)會(huì)返回?cái)?shù)值3。這個(gè)值隨后被傳入createRepeatinglterator()作為它的參數(shù),因而生成字符串"repeat"的yield語句會(huì)被執(zhí)行三次 - 無論通過何種方式調(diào)用迭代器
next()方法,數(shù)值3都不會(huì)被返回,它只存在于生成器createCombinedlterator()的內(nèi)部。但如果想輸出這個(gè)值,則可以額外添加一條yield語句
function *createNumberIterator() {
yield 1;
yield 2;
return 3;
}
function *createRepeatingIterator(count) {
for (let i=0; i < count; i++) {
yield "repeat";
}
}
function *createCombinedIterator() {
let result = yield *createNumberIterator();
yield result;
yield *createRepeatingIterator(result);
}
var iterator = createCombinedIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
[注意]
yield*也可直接應(yīng)用于字符串,例如yield* "hello",此時(shí)將使用字符串的默認(rèn)迭代器
function *createStringIterator() {
yield* "hello"
}
var iterator = createStringIterator();
console.log(iterator.next()); // "{ value: 'h', done: false }"
console.log(iterator.next()); // "{ value: 'e', done: false }"
console.log(iterator.next()); // "{ value: 'l', done: false }"
console.log(iterator.next()); // "{ value: 'l', done: false }"
console.log(iterator.next()); // "{ value: 'o', done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }" //
13.異步任務(wù)執(zhí)行
生成器令人興奮的特性多與異步編程有關(guān),
JS中的異步編程有利有弊:簡(jiǎn)單任務(wù)的異步化非常容易;而復(fù)雜任務(wù)的異步化會(huì)帶來很多管理代碼的挑戰(zhàn)。由于生成器支持在函數(shù)中暫停代碼執(zhí)行,因而可以深入挖掘異步處理的更多用法
執(zhí)行異步操作的傳統(tǒng)方式一般是調(diào)用一個(gè)函數(shù)并執(zhí)行相應(yīng)回調(diào)函數(shù)
// 從磁盤讀取文件
let fs = require("fs");
fs.readFile("config.json", function(err, contents) {
if (err) {
throw err;
}
doSomethingWith(contents);
console.log("Done");
});
- 調(diào)用
fs.readFile()方法時(shí)要求傳入要讀取的文件名和一個(gè)回調(diào)函數(shù),操作結(jié)束后會(huì)調(diào)用該回調(diào)函數(shù)并檢查是否存在錯(cuò)誤,如果沒有就可以處理返回的內(nèi)容。如果要執(zhí)行的任務(wù)很少,那么這樣的方式可以很好地完成任務(wù);如若需要嵌套回調(diào)或序列化一系列的異步操作,事情會(huì)變得非常復(fù)雜。此時(shí),生成器和yield語句就派上用場(chǎng)了
【簡(jiǎn)單任務(wù)執(zhí)行器】
由于執(zhí)行
yield語句會(huì)暫停當(dāng)前函數(shù)的執(zhí)行過程并等待下一次調(diào)用next()方法,因此可以創(chuàng)建一個(gè)函數(shù),在函數(shù)中調(diào)用生成器生成相應(yīng)的迭代器,從而在不用回調(diào)函數(shù)的基礎(chǔ)上實(shí)現(xiàn)異步調(diào)用next()方法
function run(taskDef) {
// 創(chuàng)建迭代器,讓它在別處可用
let task = taskDef();// 啟動(dòng)任務(wù)
let result = task.next();// 遞歸使用函數(shù)來保持對(duì) next() 的調(diào)用
function step() {
// 如果還有更多要做的
if (!result.done) {
result = task.next();
step();
}
}
// 開始處理過程
step();
}
- 函數(shù)
run()接受一個(gè)生成器函數(shù)作為參數(shù),這個(gè)函數(shù)定義了后續(xù)要執(zhí)行的任務(wù),生成一個(gè)迭代器并將它儲(chǔ)存在變量task中。首次調(diào)用迭代器的next()方法時(shí),返回的結(jié)果被儲(chǔ)存起來稍后繼續(xù)使用。step()函數(shù)會(huì)檢查result.done的值,如果為false則執(zhí)行迭代器的next()方法,并再次執(zhí)行step()操作。每次調(diào)用next()方法時(shí),返回的最新信息總會(huì)覆寫變量result。在代碼的最后,初始化執(zhí)行step()函數(shù)并開始整個(gè)的迭代過程,每次通過檢查result.done來確定是否有更多任務(wù)需要執(zhí)行 - 借助這個(gè)
run()函數(shù),可以像這樣執(zhí)行一個(gè)包含多條yield語句的生成器
run(function*() {
console.log(1);
yield;
console.log(2);
yield;
console.log(3);
});
- 這個(gè)示例最終會(huì)向控制臺(tái)輸出多次調(diào)用
next()方法的結(jié)果,分別為數(shù)值1、2和3。當(dāng)然,簡(jiǎn)單輸出迭代次數(shù)不足以展示迭代器高級(jí)功能的實(shí)用之處,下一步將在迭代器與調(diào)用者之間互相傳值
【向任務(wù)執(zhí)行器傳遞數(shù)據(jù)】
給任務(wù)執(zhí)行器傳遞數(shù)據(jù)的最簡(jiǎn)單辦法是,將值通過迭代器的
next()方法傳入作為yield的生成值供下次調(diào)用。在這段代碼中,只需將result.value傳入next()方法即可
function run(taskDef) { // 創(chuàng)建迭代器,讓它在別處可用
let task = taskDef(); // 啟動(dòng)任務(wù)
let result = task.next(); // 遞歸使用函數(shù)來保持對(duì) next() 的調(diào)用
function step() {
// 如果還有更多要做的
if (!result.done) {
result = task.next(result.value);
step();
}
}
// 開始處理過程
step();
}
- 現(xiàn)在
result.value作為next()方法的參數(shù)被傳入,這樣就可以在yield調(diào)用之間傳遞數(shù)據(jù)了
run(function*() {
let value = yield 1;
console.log(value); // 1
value = yield value + 3;
console.log(value); // 4
});
- 此示例會(huì)向控制臺(tái)輸出兩個(gè)數(shù)值1和4。其中,數(shù)值1取自
yield 1語句中回傳給變量value的值;而4取自給變量value加3后回傳給value的值?,F(xiàn)在數(shù)據(jù)已經(jīng)能夠在yield調(diào)用間互相傳遞了,只需一個(gè)小小改變便能支持異步調(diào)用
【異步任務(wù)執(zhí)行器】
之前的示例只是在多個(gè)
yield調(diào)用間來回傳遞靜態(tài)數(shù)據(jù),而等待一個(gè)異步過程有些不同。任務(wù)執(zhí)行器需要知曉回調(diào)函數(shù)是什么以及如何使用它。由于yield表達(dá)式會(huì)將值返回給任務(wù)執(zhí)行器,所有的函數(shù)調(diào)用都會(huì)返回一個(gè)值,因而在某種程度上這也是一個(gè)異步操作,任務(wù)執(zhí)行器會(huì)一直等待直到操作完成
- 下面定義一個(gè)異步操作
function fetchData() {
return function(callback) {
callback(null, "Hi!");
};
}
- 本示例的原意是讓任務(wù)執(zhí)行器調(diào)用的所有函數(shù)都返回一個(gè)可以執(zhí)行回調(diào)過程的函數(shù),此處
fetchData()函數(shù)的返回值是一個(gè)可接受回調(diào)函數(shù)作為參數(shù)的函數(shù),當(dāng)調(diào)用它時(shí)會(huì)傳入一個(gè)字符串"Hi!"作為回調(diào)函數(shù)的參數(shù)并執(zhí)行。參數(shù)callback需要通過任務(wù)執(zhí)行器指定,以確保回調(diào)函數(shù)執(zhí)行時(shí)可以與底層迭代器正確交互。盡管fetchData()是同步函數(shù),但簡(jiǎn)單添加一個(gè)延遲方法即可將其變?yōu)楫惒胶瘮?shù)
function fetchData() {
return function(callback) {
setTimeout(function() {
callback(null, "Hi!");
}, 50);
};
}
- 在這個(gè)版本的
fetchData()函數(shù)中,讓回調(diào)函數(shù)延遲了50ms再被調(diào)用,所以這種模式在同步和異步狀態(tài)下都運(yùn)行良好。只需保證每個(gè)要通過yield關(guān)鍵字調(diào)用的函數(shù)都按照與之相同的模式編寫 - 理解了函數(shù)中異步過程的運(yùn)作方式,可以將任務(wù)執(zhí)行器稍作修改。當(dāng)
result.value是一個(gè)函數(shù)時(shí),任務(wù)執(zhí)行器會(huì)先執(zhí)行這個(gè)函數(shù)再將結(jié)果傳入next()方法
function run(taskDef) {
// 創(chuàng)建迭代器,讓它在別處可用
let task = taskDef();// 啟動(dòng)任務(wù)
let result = task.next();// 遞歸使用函數(shù)來保持對(duì) next() 的調(diào)用
function step() {
// 如果還有更多要做的
if (!result.done) {
if (typeof result.value === "function") {
result.value(function(err, data) {
if (err) {
result = task.throw(err);
return;
}
result = task.next(data);
step();
});
} else {
result = task.next(result.value);
step();
}
}
}
// 開始處理過程
step();
}
- 通過
===操作符檢査后,如果result.value是一個(gè)函數(shù),會(huì)傳入一個(gè)回調(diào)函數(shù)作為參數(shù)調(diào)用它,回調(diào)函數(shù)遵循Node.js有關(guān)執(zhí)行錯(cuò)誤的約定:所有可能的錯(cuò)誤放在第一個(gè)參數(shù)(err)中,結(jié)果放在第二個(gè)參數(shù)中。如果傳入了err,意味著執(zhí)行過程中產(chǎn)生了錯(cuò)誤,這時(shí)通過task.throw()正確輸出錯(cuò)誤對(duì)象;如果沒有錯(cuò)誤產(chǎn)生,data被傳入task.next()作為結(jié)果儲(chǔ)存起來,并繼續(xù)執(zhí)行step()。如果result.value不是一個(gè)函數(shù),則直接將其傳入next()方法 - 現(xiàn)在,這個(gè)新版的任務(wù)執(zhí)行器已經(jīng)可以用于所有的異步任務(wù)了。在
Node.js環(huán)境中,如果要從文件中讀取一些數(shù)據(jù),需要在fs.readFile()外圍創(chuàng)建一個(gè)包裝器(wrapper),并返回一個(gè)與fetchData()類似的函數(shù)
let fs = require("fs");
function readFile(filename) {
return function(callback) {
fs.readFile(filename, callback);
};
}
- readFile()接受一個(gè)文件名作為參數(shù),返回一個(gè)可以執(zhí)行回調(diào)函數(shù)的函數(shù)?;卣{(diào)函數(shù)被直接傳入fs.readFile()方法,讀取完成后會(huì)執(zhí)行它
run(function*() {
let contents = yield readFile("config.json");
doSomethingWith(contents);
console.log("Done");
});
- 在這段代碼中沒有任何回調(diào)變量,異步的
readFile()操作卻正常執(zhí)行,除了yield關(guān)鍵字外,其他代碼與同步代碼完全一樣,只不過函數(shù)執(zhí)行的是異步操作。所以遵循相同的接口,可以編寫一些讀起來像是同步代碼的異步邏輯 - 當(dāng)然,這些示例中使用的模式也有缺點(diǎn),也就是不能百分百確認(rèn)函數(shù)中返回的其他函數(shù)一定是異步的。著眼當(dāng)下,最重要的是能理解任務(wù)執(zhí)行過程背后的理論知識(shí)