ES6中引入了許多新特性姐赡,目前大量的JavaScript項目已經(jīng)使用了ES6來進行開發(fā)硼啤,那么熟悉這些新的特性是十分必要的何暇,例如Redux-Saga中大量的使用了Iterator和generator挽懦。這篇文章總結(jié)和介紹一下ES6中的Iterator和Generator秕铛。
iterators and Generators
第一個問題什么是iterator二跋?答案很簡單粗悯, Iterator是一個object,但是含有特定的接口同欠,它有next method可以返回一個result object样傍,這個result object有兩個屬性第一個是value,代表這個迭代的值, 第二個是done铺遂,代表迭代是否結(jié)束衫哥。如果我們自己來簡單實現(xiàn)一個Iterator,它是這樣的襟锐。
function createIterator(items) {
var i = 0;
return {
next : function () {
var done = i >= (items.length)
var value = items[i++]
return {
done: done,
value: value
}
}
}
}
const items = [1,2,3]
const iteratorA = createIterator(items)
iteratorA.next() // {result:1, done: false}
那么Generator又是什么撤逢?Generator 是一個函數(shù)可以產(chǎn)生iterator。Generator函數(shù)用function關(guān)鍵字后邊帶*來表示。在函數(shù)定義上使用yield關(guān)鍵字來表示next方法調(diào)用時返回的值蚊荣。例如
function *createIterator(){
yield 1;
yield 2;
yield 3;
}
let iterator = createIterator();
console.log(iterator.next().value); //1
console.log(iterator.next().value); //2
console.log(iterator.next().value); //3
iterables
上邊介紹了什么是Iterator初狰,什么是generator,下邊再介紹一個概念iterable互例。iterable是一個有Symbol.iterator屬性的object奢入。這個symbol指向一個generator函數(shù),這個函數(shù)返回關(guān)于這個對象的iterator媳叨。在ES6中所有的集合類對象(array, set, maps)和字符串都是iterable腥光,并且有自己默認的iterator。當我們在使用 for-of時候?qū)嶋H上是利用了這些對象上的iterator,每次調(diào)用了next方法糊秆,將返回的result上的value返回回來武福。
let values = [1, 2, 3];
for (let num of values) {
console.log(num);
}
例如這段簡單的代碼,實際上調(diào)用了values上的iterator的next方法痘番,將result上的value拿出來賦給num捉片。既然是這樣我們可以采用這樣的方法來獲得默認的iterator。
let values = [1, 2, 3];
let iterator = values[Symbol.iterator]();
在ES6中對于集合類型的Object,其上定義了一些內(nèi)置的iterator汞舱,分別是伍纫;
- entries() - 返回一個返回key-value pair的iterator
- values() - 返回一個返回collection對應(yīng)值的iterator // chrome not supported
MDN - keys() - 返回一個返回collecttion對應(yīng)key的iterator
以上就是iterator和generator的一些基本概念,下邊我們來看一下一些高階應(yīng)用兵拢。
向iterator中傳遞參數(shù)
上邊的例子中我們在調(diào)用iterator的next方法都是無參數(shù)調(diào)用的,但是我們同樣可以向next方法中傳遞參數(shù)逾礁。例如這樣说铃。
function* createIterator() {
let first = yield 1;
let second = yield first + 2;
yield second + 3;
}
let i= createItreator()
i.next() // {value:1 done: false}
i.next(5) // {value: 7 done: false}
i.next(3) // {value: 6 done: false}
我們看上邊這個例子,在第二次調(diào)用中我們傳進去了5,返回值是7嘹履,這個傳進去的參數(shù)可以理解為上一次yield的返回值腻扇。注意yield本身是不返回任何值的,它只向外部產(chǎn)生值砾嫉。如果我們查看yield在英語詞典中的意思幼苛,produce or generate (a result, gain, or financial return
所以yield的值是向外產(chǎn)生值。所以在第一次next后 first的值依舊是undefined焕刮。但是向next中傳遞參數(shù)舶沿,這個參數(shù)代表我們想要上一次yield在generator函數(shù)中的值。所以在第二次next后 返回值的value就是7(5+2)了配并。第三例子同理括荡。所以基于上邊的原因我們向第一個next函數(shù)中傳入任何值都是沒有意義的。我們變化一下再看
function* createIterator() {
yield 1;
let first;
let second = yield first + 2;
yield second + 3;
}
i.next() // {value:1 done: false}
i.next(5) // {value: NaN done: false}
i.next(3) // {value: 6 done: false}
在第二個next中我們的返回是NaN, 為什么呢溉旋?這是因為first是Undefined畸冲,第一次的yield并沒有給first賦值。所以在yeild中的執(zhí)行順序是每一次執(zhí)行到相應(yīng)的yield就完了,下次繼續(xù)向下執(zhí)行邑闲。
在Iterator中Throw Error
在iterator中我們可以來throw error 來達到控制執(zhí)行的目的算行。例如上邊一個例子。
function* createIterator() {
let first = yield 1;
let second = yield first + 2;
yield second + 3;
}
let i= createItreator()
i.next() // {value:1 done: false}
i.next(5) // {value: 7 done: false}
i.throw(new Error('error')) // error thrown done is set to true after throw error
Generator function中的Return
同樣在generator 我們可以使用 return來返回苫耸。
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 }"
第一次next后已經(jīng)結(jié)束了所以 我們第二次next后done就已經(jīng)是true了州邢。
Generator 和 Iterator的應(yīng)用實例:Task Runner
我們可以使用generator和Iterator來實現(xiàn)一個task runner,可以讓我們不用手動的next,而是一次執(zhí)行結(jié)束鲸阔。代碼如下:
function run(taskDef) {
let task = taskDef();
let value = task.next()
function step() {
if (!value.done) {
value = task.next(value.value)
step()
}
}
step()
}
run(function*(){
let first = yield 1;
let second = yield first + 3;
yield second + 4;
})
上邊就是一個例子偷霉,這樣定義的run function就可以順序執(zhí)行這些generator定義的步驟。
實際上generator和Iterator最為實際的作用是可以控制異步函數(shù)的執(zhí)行褐筛,下邊我們可以簡單的例子类少。
function run(taskDef) {
let task = taskDef();
let result = task.next()
function step() {
if (!result.done) {
if (typeof result.value === "function") {
result.value(function(err, data) {
if (err) {
console.log('err', err);
task.throw(err)
return
}
console.log('err', data);
result = task.next(data);
step()
})
} else {
result = task.next(result.value)
step()
}
}
}
step()
}
let fs = require("fs");
function readFile(filename) {
return function (callback) {
fs.readFile(filename, callback);
};
}
run(function* () {
let contents = yield readFile("abc.json");
console.log(contents);
console.log("Done");
});
首先我們定義了一個task runner run function
在其中當發(fā)現(xiàn)result中的value是function的時候,就執(zhí)行這個function, 并且在異步函數(shù)的callback中渔扎,當沒有error的時候執(zhí)行下一步硫狞。
在看我們的ReadFile function,fs模塊中的readFile是一個異步的函數(shù)晃痴,而在這里我們將其進行了封裝成為一個新的函數(shù)残吩。讓其返回一個function給在task runner中使用。那么在我們的generator函數(shù)中倘核,我們看上去的代碼就和同步的一樣了泣侮,先readfile,完成后將其輸出紧唱。這樣使用Iterator和generator可以幫助我們寫出一個比較好看的異步執(zhí)行函數(shù)活尊。