迭代器是es6中一個重要的概念,不少新特性都是基於迭代器概念而鋪開的。爲了更加方便的建立自定義的迭代器,es6引入了生成器 (Generator)
的概念。它是一種能夠返回迭代器的特殊函數。有了生成器及它的特性可讓咱們建立更加簡潔的異步代碼。git
經過 function
關鍵字後面的星號(*)
來表示,函數體用 yield
關鍵字來控制迭代器每次 next()
返回結果:es6
function* createIterator() {
yield 1;
yield 2;
yield 3;
}
let iterator = createIterator();
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 }
複製代碼
經過生成器生成的迭代器每次調用 next()
執行函數代碼時 ,每次執行 yield
語句完後就會自動中止執行。直到再次調用 next()
方法纔會繼續執行。github
function* createIterator() {
console.log(1);
yield;
console.log(2);
yield;
console.log(3);
}
let iterator = createIterator();
iterator.next(); // 1
iterator.next(); // 2
複製代碼
yield
關鍵字只能在生成器內部使用,嵌套的函數也不行:npm
function* createIterator(items) {
items.forEach(function (item) {
// SyntaxError: Unexpected identifier
yield item;
})
}
複製代碼
在對象裏面定義生成器函數:異步
let obj = {
createIterator: function* (items) {
// ...
}
}
// 用es6方式
let obj = {
*createIterator(items) {
// ...
}
}
複製代碼
注意,生成器函數不支持箭頭函數寫法ide
能夠給迭代器 next()
方法傳遞一個參數,這個參數的值會替代生成器內部上一條yield
語句的返回值:函數
function* createIterator() {
let first = yield 1;
let second = yield first + 2;
yield second + 3;
}
let iterator = createIterator();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next(3)); // { value: 5, done: false }
console.log(iterator.next(5)); // { value: 8, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
複製代碼
第二次調用 next()
傳入4,會做爲上一條 yield
語句的返回值,此時first的值爲3,而不是1,因此第二次 next
的返回值爲5。以此類推,第3次 next()
傳入5,返回值爲8。post
注意,第一次調用 next()
傳入參數會被忽略。運行的流程能夠以下圖:fetch
迭代器除了 next()
方法,還有利用 throw()
拋出一個Error對象。錯誤被拋出後,生成器函數的後面代碼會中止執行:ui
function* createIterator() {
let first = yield 1;
let second = yield first + 2;
yield second + 3;
}
let iterator = createIterator();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next(3)); // { value: 5, done: false }
console.log(iterator.throw(new Error('boom'))); // 從生成器中拋出錯誤
複製代碼
在生成器函數內能夠用 try...catch
來捕捉錯誤,後續代碼才能繼續執行:
function* createIterator() {
let first = yield 1;
let second;
try {
second = yield first + 2;
} catch (e) {
second = 6;
}
yield second + 3;
}
let iterator = createIterator();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next(3)); // { value: 5, done: false }
console.log(iterator.throw(new Error('boom'))); // { value: 9, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
複製代碼
由於生成器也是一個函數,因此能夠用 return
返回值。在 return
後,結果對象的done當即變爲 true
,value爲返回的值。後續 yield
語句將不會執行:
function* createIterator() {
yield 1;
return 'done';
yield 2;
}
let iterator = createIterator();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 'done', done: true }
console.log(iterator.next()); // { value: undefined, done: true }
複製代碼
return
返回值只獲取一次,後續調用 next()
都是返回 undefiend
。
委託生成器是指在生成器的內用 yield*
語法接上另一個生成器函數,把數據生成的過程委託給其餘迭代器:
function* createNumber() {
yield 1;
yield 2;
}
function* createColor() {
yield 'red';
}
function* combine() {
yield* createNumber();
yield* createColor();
return 'combine';
}
let iterator = combine();
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: 'combine', done: true }
複製代碼
咱們用 setTimeout()
來模擬一個異步任務:
function fetchData(url, cb) {
setTimeout(() => {
cb({ code: 0, data: url });
}, 1000);
}
複製代碼
把上面的函數改爲返回能夠接收回調的函數:
function fetchData(url) {
return (cb) => {
setTimeout(() => {
cb({ code: 0, data: url });
}, 1000);
}
}
複製代碼
咱們有一個異步任務的生成器函數:
function* gen() {
let res1 = yield fetchData('http://www.baidu.com');
let res2 = yield fetchData('http://www.inoob.xyz');
console.log(res1.data + ' ' + res2.data);
}
複製代碼
要讓上面的生成器函數正確執行,咱們須要這樣調用:
let g = gen();
g.next().value(function(data) {
var r2 = g.next(data);
r2.value(function(data) {
g.next(data);
});
});
複製代碼
經過在回調函數的執行把控制權從新回到生成器函數,繼續執行函數到下一條 yield
。咱們用遞歸的方式改寫執行函數:
function run(gen) {
let g = gen();
function next(data) {
let result = g.next(data);
if (result.done) return;
if (typeof result.value === 'function') {
result.next(data);
} else {
next(result.value);
}
}
next();
}
run(gen);
複製代碼
其實,上面的自動執行生成器函數的方法只適用於回調形式的異步任務,還要考慮返回Promise
形式的異步任務,而且要處理異常的狀況。這裏推薦一個npm庫,該庫已經兼容全部狀況自動執行 Generator
函數。 >>>github地址