在介绍生成器函数之前先了解一下ES6的一个关键字,名为yield
yield关键字,可以让代码在其出现的地方暂停执行,它只能在生成器函数内部使用。
生成器函数
生成器函数的语法比较简单在普通函数声明的时候在函数名前面添加一个*即可,如下所示:
function *gen(){}
当函数被调用时会返回一个返回值,但是生成器函数与普通函数的返回值有些区别。
普通函数如果没有使用return返回时,接收到的是undefined,而生成器函数不管有没有return都返回一个对象,这个对象被称为迭代器,我们可以通过这个对象控制迭代器的执行。通常,使用迭代器的next方法,让其继续执行。
let x = 0;
function *gen(){
console.log(x);
yield x++;
}
let itr = gen();
console.log(itr)
根据上面的结果我们可以知道,生成器函数在被调用时并不会开始执行代码。得到了一个对象,这也验证了上文所言。
接下来,我们让它继续执行,需要调用next方法。
let x = 0;
function *gen(){
console.log(x);
yield x++;
}
let itr = gen();
console.log(itr)
itr.next();
console.log(x)
从上面的结果来看,next方法也验证了前文所言。
当我们调用next方法时,next方法也会返回一个对象,这个对象包含一个value属性,value属性的值是当前yield语句的结果。
let x = 0;
function *gen(){
console.log(x);
yield x++;
}
let itr = gen();
console.log(itr)
let res = itr.next();
console.log(res)
我们根据结果可以看到,除了value属性,还可以看到一个done属性,done属性的值表示迭代器的完成状态,为true表明完成迭代,为false,表明没有完成迭代。
如果一个生成器函数中使用return,我们需要得到return的值,这时候需要使用next方法返回的对象的value属性得到返回的值。
let x = 0;
function *gen(){
console.log(x);
yield x++;
return x;
}
let itr = gen();
let res = itr.next();
console.log(res.value);
res = itr.next();
console.log(res.value,res);
res = itr.next();
console.log(res);
根据输出的第三行,我们可以看到输出的值是1,与预想的一样,同时done属性也为true,说明运行到return语句。当我们再次运行next方法时,此时函数已经执行完毕,不在执行,也就没有返回值,因此是undefined。
但是在返回1之前,还有一个值,这个值是yield x++; 语句的返回值,x++的返回值是0,因此value属性是0
这样一个完整的迭代器运行流程我们就了解了。
接下来,我们再了解一下next方法。之前我们知道next方法返回一个对象,可以通过value属性得到返回值。
next方法也可以接收一个值
let x = 0;
function *gen(){
console.log(yield);
x++;
return x;
}
let itr = gen();
let res = itr.next(6);
console.log(res);
res = itr.next(7);
console.log(res);
根据结果我们可以知道,第一个next传递参数并不会被接收,规范和所有兼容浏览器都会默默丢弃传递给第一个next()的任何东西。传值过去仍然不是一个好思路,因为你创建了沉默的无效代码,这会让人迷惑。因此,启动生成器时一定要用不带参数的next()。
只有从第二个next开始传递的参数才会被接收,由此,我们可以总结,next传递的值被上一个yield接收,next与field相差一个间隔。
那么为什么会产生这个差距呢?
因为第一个next(…)总是启动一个生成器,并运行到第一个yield处。不过,是第二个next(…)调用完成第一个被暂停的yield表达式,第三个next(…)调用完成第二个yield,以此类推。
以上就是生成器函数的基本使用,那么生成器函数到底有什么用或者说能够应用在什么场景呢?
生成器函数可以应用在异步任务中,当一个异步任务没有返回值时我们可以让程序在此处暂停当有返回值时在调用next继续执行。
示例
function run(gen) {
var args = [].slice.call(arguments, 1), it;
// 在当前上下文中初始化生成器
it = gen.apply(this, args);
// 返回一个promise用于生成器完成
return Promise.resolve()
.then(function handleNext(value) {
// 对下一个yield出的值运行
var next = it.next(value);
return (function handleResult(next) {
// 生成器运行完毕了吗?
if (next.done) {
return next.value;
}
// 否则继续运行
else {
return Promise.resolve(next.value)
.then(
// 成功就恢复异步循环,把决议的值发回生成器
handleNext,
// 如果value是被拒绝的promise,
// 就把错误传回生成器进行出错处理
function handleErr(err) {
return Promise.resolve(it.throw(err)).then(handleResult);
}
);
}
})(next);
});
}