Generator 学习
1. The Basics Of ES6 Generators
函数里用 yield 关键字来暂停函数运行。外部没有任何东西可以暂停它。所以当它暂停了,必须有外部控制来重新启动它。 通常一个函数,你刚开始传递参数进去,然后 用 return 来返回。而 Generator 函数,通过 yield 传递消息出去,每次启动的时候传递消息进去,传递的值是 yield 表达式的结果。
Generator 返回的是一个迭代器
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
}
var it = foo();
yeild 调用
由于第 1 次调用 it.next(5)
时,因为并没有 yeild 表达式接收 5 这个值,都没有 yeild 执行过。 所以只能初始化 foo(5)
, 传递值 5 进去给 x。
function* foo(x) {
var y = 2 * (yield x + 1);
var z = yield y / 3;
return x + y + z;
}
var it = foo(5);
// note: not sending anything into `next()` here
console.log(it.next()); // { value:6, done:false }
console.log(it.next(12)); // { value:8, done:false }
console.log(it.next(13)); // { value:42, done:true }
for..of
ES6 也可以用 for..of
来迭代 generator
, 但是注意下面的例子, return 返回的值并不能被迭代出, 所以 generator
里不建议用 return
; 另外一个角度理解, 函数的最后 return undefined
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (var v of foo()) {
console.log(v);
}
// 1 2 3 4 5
console.log(v); // still `5`, not `6`
2. Diving Deeper With ES6 Generators
如何往 Generator 里面抛入一个异常; 注意抛进去的异常, 并不是赋值给了 x, 而是给了 catch 里的 err。
function* foo() {
try {
var x = yield 3;
console.log("x: " + x); // may never get here!
} catch (err) {
console.log("Error: " + err);
}
}
var it = foo();
var res = it.next(); // { value:3, done:false }
it.throw("Oops!"); // Error: Oops!
如果你往 Generator 里面抛异常, Generator 没有用 try...catch
捕获异常的话,异常就会抛出来,可以在外面捕获这个异常
function* foo() {
var x = yield 3;
console.log("x: " + x); // may never get here!
}
var it = foo();
var res = it.next(); // { value:3, done:false }
try {
it.throw("Oops!");
} catch (e) {
console.log("Error: " + e); // Error: Oops!
}
或者
function* foo() {
var x = yield 3;
var y = x.toUpperCase(); // could be a TypeError error!
yield y;
}
var it = foo();
it.next(); // { value:3, done:false }
try {
it.next(42); // `42` won't have `toUpperCase()`
} catch (err) {
console.log(err); // TypeError (from `toUpperCase()` call)
}
这个就相当于普通函数如果有异常,在外面其实也可以捕获:
function f1() {
console.log(aaaa);
}
try {
f1();
} catch (e) {
console.log(e); // 'ReferenceError: a is not defined'
}
3. Delegating Generators
如何在 generator 内部去调用另外一个 generator, 通过 yield *
当代码遇到 yield*
, 会去实例化 foo(), 所以实际上是 yielding/delegating 另外一个 generator。 当 *foo()
结束,控制权转回原来的 generator;
function* foo() {
yield 3;
yield 4;
}
function* bar() {
yield 1;
yield 2;
yield* foo(); // `yield *` delegates iteration control to `foo()`
yield 5;
}
for (var v of bar()) {
console.log(v);
}
// 1 2 3 4 5
如果不用 for..of
, 正常的使用 next(..)
, 看下下面的例子
function* foo() {
var z = yield 3;
var w = yield 4;
console.log("z: " + z + ", w: " + w);
}
function* bar() {
var x = yield 1;
var y = yield 2;
yield* foo(); // 相当于 var ffff = foo(); ffff.next();
var v = yield 5;
console.log("x: " + x + ", y: " + y + ", v: " + v);
}
var it = bar();
it.next(); // { value:1, done:false }
it.next("X"); // { value:2, done:false }
it.next("Y"); // { value:3, done:false }
it.next("Z"); // { value:4, done:false }
it.next("W"); // { value:5, done:false }
// z: Z, w: W
it.next("V"); // { value:undefined, done:true }
// x: X, y: Y, v: V
另外一个情况是 yield*
可以接收从委托的 generator
里的 return
的值, 注意并没有输出 { value:'foo', done:false }
function* foo() {
yield 2;
yield 3;
return "foo"; // return value back to `yield*` expression
}
function* bar() {
yield 1;
var v = yield* foo();
console.log("v: " + v);
yield 4;
}
var it = bar();
it.next(); // { value:1, done:false }
it.next(); // { value:2, done:false }
it.next(); // { value:3, done:false }
it.next(); // "v: foo" { value:4, done:false }
it.next(); // { value:undefined, done:true }
但是如果只迭代有 return
值的 foo, 则可以迭代出 { value:'foo', done:false }
如果没有 return "foo"
, 可以理解函数最后有一个 return undefined;
function* foo() {
yield 1;
yield 2;
return "foo"; // return value back to `yield*` expression
}
var it = foo();
it.next(); // { value:1, done:false }
it.next(); // { value:2, done:false }
it.next(); // { value:'foo', done:true }
委托如果遇到报错, 是怎么传递的, 看下面的例子。 foo
里面的报错在 bar
里接收了。 注意下面的代码, it.throw( "Uh oh!" )
会跳到了下一个 yield;
function* foo() {
try {
yield 2;
} catch (err) {
console.log("foo caught: " + err);
}
yield; // pause
// now, throw another error
throw "Oops!";
}
function* bar() {
yield 1;
try {
yield* foo();
} catch (err) {
console.log("bar caught: " + err);
}
}
var it = bar();
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.throw("Uh oh!"); // foo caught: Uh oh!, {value: undefined, done: false}
it.next(); // bar caught: Oops! {value: undefined, done: true}
4.一些例子
function* bar() {
yield 1;
try {
console.log(a);
yield 2;
} catch (err) {
console.log("bar caught: " + err);
}
}
var it = bar();
it.next(); // {value: 1, done: false}
/*
到不了 yiled 2;
bar caught: ReferenceError: a is not defined;
{value: undefined, done: true};
*/
it.next(2);
5.总结
yield*
允许你委托迭代控制从当前的 generator 到另外一个 generator, yield*
扮演一个传递的角色, 可以同时传递信息和报错。
throw 的例子, 下面的 2 个例子是等价的 所以 it.throw('xxx')
的时候可以大概理解成 it.next(throw 'xxx')
;
例子 1:
function* bar() {
yield 1;
try {
yield 2;
} catch (err) {
console.log("bar caught: " + err);
}
}
var it = bar();
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.throw("aaa"); // bar caught: aaa; {value: undefined, done: false}
例子 2:
function* bar() {
yield 1;
try {
yield 2;
throw "aaa";
} catch (err) {
console.log("bar caught: " + err);
}
}
var it = bar();
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // bar caught: aaa; {value: undefined, done: false}
6. Going Async With ES6 Generators
Simplest Async
通过 request 里面调用 it.next
来进行下一步。yeild 不用关心 request 里面是怎么拿最新的 data 的。 注意 makeAjaxCall 里的代码不能立即执行 callback, 否则有个错误, 因为 generator 从技术上来说还没有在停止的状态,因为在那个时刻 generator 还在运行当中,yeild 还没有处理。
var id = 1;
function makeAjaxCall(url, callback) {
setTimeout(() => {
callback(
JSON.stringify({
value: url,
id: id++
})
);
}, 1000);
}
function request(url) {
makeAjaxCall(url, function (response) {
it.next(response);
});
}
function* main() {
var result1 = yield request("http://some.url.1");
var data = JSON.parse(result1);
var result2 = yield request("http://some.url.2?id=" + data.id);
var resp = JSON.parse(result2);
console.log("The value you asked for: " + resp.value);
}
var it = main();
it.next();
例子 2 也就是说 yield 里面的函数不能立刻执行 it.next
function doNext() {
it.next();
}
function* foo() {
var res = yield doNext();
console.log(res);
res = yield 2;
console.log(res);
res = yield 3;
console.log(res);
}
var it = foo();
it.next();
Better Async
目前异步的问题有 3 个: 1: 对于异常的处理不清晰。虽然我们能用 it.throw
来处理异常, 然后用 try..catch
来捕获异常, 但是如果我们做很多的 generators, 代码不太好用。
2: makeAjaxCall(..)
的问题是不受我们控制,如果 callback 执行多次,或者 成功和失败同时执行。那么我们的 generator 就会失控
3: 如何触发多个任务在一个 generator yeild
里面。
如何解决这个问题,通过 yeild out promise
, 通过定义一个 runGenerator
方法。
function request(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(url);
}, 2000);
});
}
runGenerator(function* main() {
var result1 = yield request("http://some.url.1");
console.log("result1=" + result1);
var result2 = yield request("http://some.url.2?id=");
console.log("result2=" + result2);
var r3 = yield 2;
console.log(r3);
});
function runGenerator(ge) {
let it = ge();
const loop = (data) => {
let p = it.next(data);
if (p.done) {
return;
}
if (p.value.then) {
p.value.then(loop);
} else {
/*
这里其实不用 setTimeout 也不会报错,
但是为了跟上面的 promise.then 一样是异步的, 所以加了个 setTimeout
*/
setTimeout(() => {
loop(p.value);
});
}
};
loop();
}