yield是什么
- yield是ES6的新关键字,可以使生成器函数执行暂停,yield关键字后面的表达式的值返回给生成器的调用者。它可以被认为是一个基于生成器版本的return关键字。
- yield关键字实际返回一个IteratorResult(迭代器)对象,它有两个属性,value和done,分别代表返回值和是否完成。点击查看更多关于IteratorResult的知识
- yield无法单独使用,需要配合generator(生成器)的其他函数,如next,懒汉式操作,展现强大的主动控制特性。
1.yield的写法
function* fn() {
yield 1;
yield 2;
yield 3;
return 4;
yield 5;
}
let iterator = fn();
let j = 5;
let timer = setInterval(() => {
const i = iterator.next();
if (j-- == 0) {
clearInterval(timer)
timer = null;
return
}
console.log(i)
}, 1000)
//结果:
// { value: 1, done: false }
// { value: 2, done: false }
// { value: 3, done: false }
// { value: 4, done: true }
// { value: undefined, done: true }
- yield必须写在生成器函数generator中;
- yield可以用来加强控制,懒汉式加载;
- 需要next()函数配合使用,每次调用会执行下一个yield,返回两个值:分别是value和done,代表迭代结果和是否完成;
- return之后的yield不会再执行;
- return就代表着迭代完成
- 没有写retrun的其实函数内部默认返回了return undefined
- next()可无限调用,但既定循环完成之后总是返回undeinded
2.next()函数及传参
function* fn(_name) {
let name = yield _name; //yield默认返回undefined,想要返回其他值,则通过.next(value)来传递
let age = yield name;
return {
name,
age
};
}
let i = fn('henry');
console.log(i.next());
console.log(i.next());
console.log(i.next(26)); //只能通过next赋值
//结果:
// { value: 'henry', done: false }
// { value: undefined, done: false }
// { value: { name: undefined, age: 26 }, done: true }
从上面的结果可以看出,每一次.next()括号里面的值都会临时赋值给generator函数中上一个yield,第一次next因为没有上一个yield,所有不会有临时赋值行为。
上面的例子中,第二次next没有传值,所以fn函数中的第一个yield后面的_name会被临时赋值成undefined,所以name等于undefined;第三次next传值26,所以第二个yield后面的name会被临时赋值成26,所以age等于26;但是无论是第二次next的_name或者第三次next的name的实际值都不会真的被赋值,只是临时性的等价于。
从而得出以下结论:
- 函数next()是个迭代器对象,传参可以缺省,缺省时相当于undefined,默认调用函数;
- yield并不能直接生产值,而是产生一个等待输出的函数;
- next()可以带一个参数,该参数会被认为是上一个yield整体的返回值。
next的意义在于,可以在不同阶段从外部直接向内部注入不同的值来调整函数的行为(这一点是其他循环很难做到的,或要付出较大的代价才可以做到)
PS:另外有点要注意的是,如果yield在其他表达式中,需要用()单独括起来,例如:
console.log('my qq:' + yield '123456') //报错 SyntaxError:Unexpected...
console.log('my qq:' + (yield '123456'))
3.抛异常
function * fn(){
let qq;
try{
qq = yield; // yield默认返回undefined,不会抛异常
}catch(e){
//内部捕获异常,则不会像外抛出
console.log('error1')
}
console.log(qq)
}
let g = fn();
g.next()
try{
g.throw('error!')
}catch(e){
//内部不捕获异常,则会像外抛出
console.log('error2')
}
4.实际应用
yield可以用generator实现同步方式编写异步流程,比Promise更优雅;
function asyncFun(name) {
return new Promise(function(resolve) {
setTimeout(() => {
resolve('my name is ' + name)
}, 1000)
})
}
function sum(a, b) {
return new Promise(function(resolve) {
setTimeout(() => {
console.log('sum')
resolve(a + b)
}, 2000)
})
}
// // 用promise实现
// function fn(name) {
// sum(1, 6).then(function(num) {
// if (num > 6) {
// asyncFun(name).then(function(v) {
// console.log(v)
// })
// } else {
// console.log('error!')
// }
// })
// }
// fn('henry');
//用generator实现
function* fn(name) {
if ((yield sum(1, 6)) > 6) {
console.log(yield asyncFun(name))
} else {
console.log('error!')
}
}
const gf = fn('henry');
// 编写一个工具函数,用于执行generator函数
function exec(gf, value) {
let result = gf.next(value)
console.log('result', result)
if (!result.done) {
if (result.value instanceof Promise) {
result.value.then(function(v) {
exec(gf, v)
})
} else {
exec(gf, result.value)
}
}
}
exec(gf)
用yield实现异步写法其实很简单,在有异步操作的前面加yield就可以了,比较麻烦的是需要写一个工具函数去执行generator函数,这里我们只是模拟了Promise的异步,可能还有其他,所以我们可以引入比较成熟的第三方库——‘co’;
首先要npm安装以下co;
npm install co;
然后就可以引入了
const co = require('co');
function asyncFun(name) {
return new Promise(function(resolve) {
setTimeout(() => {
resolve('my name is ' + name)
}, 1000)
})
}
function sum(a, b) {
return new Promise(function(resolve) {
setTimeout(() => {
console.log('sum')
resolve(a + b)
}, 2000)
})
}
function* fn(name) {
if ((yield sum(1, 6)) > 6) {
console.log(yield asyncFun(name))
} else {
console.log('error!')
}
}
var fnx = co.wrap(fn)
fnx('henry')
轻轻松松搞定了,更多关于co库的,点击这里查看项目
最后的最后,我们来做道题目吧!
function* test(x) {
var y = 2 * (yield(x + 3));
var z = yield(y / 4);
console.log('x:' + x + ',y:' + y + ',z:' + z)
return (x + y + z)
}
var it = test(5);
console.log(it.next())
console.log(it.next())
console.log(it.next())
var gt = test(5)
console.log(gt.next())
console.log(gt.next(16))
console.log(gt.next(21))
打印结果是:
{ value: 8, done: false }
{ value: NaN, done: false }
x:5,y:NaN,z:undefined
{ value: NaN, done: true }
{ value: 8, done: false }
{ value: 8, done: false }
x:5,y:32,z:21
{ value: 58, done: true }
分析请看评论