Generattor函数的yield next双向通信
function *foo(x) {
var y = x * (yield "Hello"); // <-- yield一个值!
return y;
}
var it = foo( 6 );
var res = it.next(); // 第一个next(),并不传入任何东西
res.value; // "Hello"
res = it.next( 7 ); // 向等待的yield传入7
res.value; // 42
复制代码
为了较为清晰的理解这个过程,特意做了一个简单的图示,加深自己的记忆。
这个过程就好像是丝绸之路上的商人之间的贸易一样,next从yield哪里拿到一些东西同时也会给yield一些东西。多个迭代器
每次构建一个迭代器 ,实际上就隐式构建了生成器的一个实例,通过这个迭代器 来控制的是这个生成器实例
function *foo() {
var x = yield 2;
z++;
var y = yield (x * z);
console.log( x, y, z );
}
var z = 1;
var it1 = foo();
var it2 = foo();
var val1 = it1.next().value; // 2 <-- yield 2
var val2 = it2.next().value; // 2 <-- yield 2
val1 = it1.next( val2 * 10 ).value; // 40 <-- x:20, z:2
val2 = it2.next( val1 * 5 ).value; // 600 <-- x:200, z:3
it1.next( val2 / 2 ); // y:300
// 20 300 3
it2.next( val1 / 4 ); // y:10
// 200 10 3
复制代码
迭代器深入理解
如果一个对象包含一个可以迭代的迭代器(iterator),那么这个对象就是一个iterable(可迭代)
从一个iterable中回去迭代器的方法:
// 以数组为例
var a = [1,2,3]
var it = a[Symbol.iterator]();
it.next().value // 1
it.next().value // 2
it.next().value // 3
复制代码
每一个iterable中都有一个函数Symbol.iterator。调用这个函数会返回一个迭代器。
for of
循环可以自动调用Symbol.iterator函数来构建一个迭代器。
让我们手动实现一个iterator(同时也是一个iterable)
var something = (function() {
let val
return {
// something对象是含有一个Symbol.iterator函数的,所以something是一个iterable;
// 这个Symbol.iterator函数返回的就是something本身,而它本身是有next()方法,所以实际上something本身也是一个迭代器(iterator)
[Symbol.iterator]: function() {return this},
next: function() {
val = val ? val + 2 : 1
return {
// 这里一直返回false,所以这个迭代器没有终点
done: false, value: val
}
}
}
})
复制代码
真正的iterator可以具有三个方法next, return, throw;我们自己实现的iterator可以按照需要省略return和throw
- return方法: 如果for...of循环提前退出(通常是因为出错,或者有break语句)
var something = (function() {
let val
return {
// something对象是含有一个Symbol.iterator函数的,所以something是一个iterable;
// 这个Symbol.iterator函数返回的就是something本身,而它本身是有next()方法,所以实际上something本身也是一个迭代器(iterator)
[Symbol.iterator]: function() {return this},
next: function() {
val = val ? val + 2 : 1
return {
// 这里一直返回false,所以这个迭代器没有终点
done: false, value: val
}
},
return() {
console.log('return 被触发')
return {done: true}
}
}
})
for(let i of something()) {
if(i > 10) {break}
console.log(i)
}
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9
// return 被触发
复制代码
-
throw方法主要是配合生成器函数一起使用,一般的iterator用不到这个方法。 next()、throw()、return()在生成器函数中这三个方法本质上是一样的, 都是让Generator函数恢复执行,并且使用不同的语句替换yield表达式。
next()是将一个值传递给yield
const g = function* (x, y) { let result = yield x + y; return result; }; const gen = g(1, 2); gen.next(); // Object {value: 3, done: false} gen.next(1); // Object {value: 1, done: true} // 相当于将 let result = yield x + y // 替换成 let result = 1; 复制代码
throw()是将一个错误传递给yield
gen.throw(new Error('出错了')); // Uncaught Error: 出错了 // 相当于将 let result = yield x + y // 替换成 let result = throw(new Error('出错了')); 复制代码
return()是将return语句传递给yield
gen.return(2); // Object {value: 2, done: true} // 相当于将 let result = yield x + y // 替换成 let result = return 2; 复制代码
生成器(Generator) vs 迭代器
生成器并不是一个iterater,它执行的结果才是一个iterator
function * foo() {}
// it是一个itreator
var it = foo()
复制代码
所以我们尝试用生成器实现上面的something:
function *something() {
var val
// 在生成器中使用while..true并没有问题
while(true) {
val = val ? val + 2 : 1
yield val
}
}
复制代码
这个实现方式跟我们之前的闭包方式相比更加简洁,不需要闭包来保持变量状态了。
for (let i of something()) {
if(i > 10){break}
console.log(i)
}
复制代码
前面我们说过something()生成的是一个iterator,而for...of需要的是一个iterable; 实际上生成器something()生成的iterator同时也是一个iterable,其内部的Symbol.iterator实际上也是类似于return this 的做法。
生成器与Promise的完美结合
生成器函数给了我们一个看似同步的流程控制代码配合Promise(可信任可组合)的组合可能是js新世界中最美妙的事情。
所以,你应该想到了,ES78(ES2017)中提供的async/await正式这两者完美结合的语法级支持。 实际上,asyn函数不过是Generator(生成器)函数的一个语法糖
// 手动结合Generator和Promise
// 这里不直接定义promise而是通过foo返回是因为promise在定义的时候就会执行
function foo() {
return new Promise(function(resolve, reject) {
resolve(10)
})
}
function *gen() {
try{
let text = yield foo()
console.log(text)
} catch(err) {
console.log(err)
}
}
let it = gen()
let p =it.next().value
p.then(
function(res) {
it.next(res)
},
function(err) {
it.throw(err)
}
)
// 10
复制代码
可以看到手动结合Promise和Generator是多么的繁琐;虽然带来了同步的流程和可信任可组合的Promsie,这也不是我们愿意看到的。
看看async/await如何实现上述过程
function foo() {
return new Promise(function(resolve, reject) {
resolve(10)
})
}
async function aw() {
let text = await foo()
console.log(text)
}
aw() // 10
复制代码
是不是清爽了很多,async/await实际上是把Generator的启动步骤和Promsie内部的next()实现细节隐藏到语法糖中,留给我们一个清爽的世界。如果希望了解实现细节,我们不放手动模仿以下这个语法糖函数
示例来自‘你不知道的javascript(中卷)’
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);
} );
}
复制代码