生成器Generator
迭代器是es6中一个重要的概念,很多新特性都是基于迭代器概念而铺开的。为了更加方便的创建自定义的迭代器,es6引入了生成器 (Generator)
的概念。它是一种可以返回迭代器的特殊函数。有了生成器及它的特性可以让我们创建更加简洁的异步代码。
基本概念
通过 function
关键字后面的星号(*)
来表示,函数体用 yield
关键字来控制迭代器每次 next()
返回结果:
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()
方法才会继续执行。
function* createIterator() {
console.log(1);
yield;
console.log(2);
yield;
console.log(3);
}
let iterator = createIterator();
iterator.next(); // 1
iterator.next(); // 2
yield
关键字只能在生成器内部使用,嵌套的函数也不行:
function* createIterator(items) {
items.forEach(function (item) {
// SyntaxError: Unexpected identifier
yield item;
})
}
在对象里面定义生成器函数:
let obj = {
createIterator: function* (items) {
// ...
}
}
// 用es6方式
let obj = {
*createIterator(items) {
// ...
}
}
注意,生成器函数不支持箭头函数写法
高级迭代器用法
迭代器传参
可以给迭代器 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。
注意,第一次调用 next()
传入参数会被忽略。运行的流程可以如下图:
在迭代器抛出错误
迭代器除了 next()
方法,还有利用 throw()
抛出一个Error对象。错误被抛出后,生成器函数的后面代码会停止执行:
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.value(next);
} else {
next(result.value);
}
}
next();
}
run(gen);
其实,上面的自动执行生成器函数的方法只适用于回调形式的异步任务,还要考虑返回Promise
形式的异步任务,并且要处理异常的情况。这里推荐一个npm库,该库已经兼容所有情况自动执行 Generator
函数。