提示
: 本文是 github 上《Understanding ECMAScript 6》 的笔记整理,代码示例也来源于此。你们有时间能够直接读这本书。虽是英文,但通俗易懂,很是推荐。git
前情:
在上一篇文章 你知道为何会有 Generator 吗 里,我抛砖引玉,介绍了 generator
产生的缘由。当时就有伙伴指出 “Generator是用来模拟多线程的休眠机制的”、 “Generator运行是惰性的”。那时我就说高级篇里会有介绍,这里就好好说一下。es6
摘要:
这里的重点,首先是如何与generator
里通讯,一是用 next()
传参,二是还能够用 throw()
,不一样的是它是往里抛错; 其次是有 yield
赋值语句时, generator
内部的执行顺序; 最后会是怎么用同步的方式写异步(有可能像 co
哦)。github
原文地址redux
若是对 generator
不太熟的,能够先看看 这里promise
简单说就是能够往next
传参数,而generator
里 yield
处能够接收到这个参数, 以下例子:bash
function *createIterator() {
let first = yield 1;
let second = yield first + 2; // 4 + 2
yield second + 3; // 5 + 3
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next(4)); // "{ value: 6, done: false }"
console.log(iterator.next(5)); // "{ value: 8, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
复制代码
要很明白地解释上面的执行过程,可借助这张图:多线程
颜色相同的是同一次迭代里执行的,由浅到深,表示迭代的前后顺序。如:异步
next()
, 执行 yield 1
到中止,返回 { value: 1, done: false }
。注意,这时赋值语句 let fisrt = ...
没有执行;next(4)
, 先将参数 4
传入上一次 yield
处,可理解为:let first = yield 1;
=>
let first = 4;
复制代码
再从上次停顿的地方开始执行,就是说先执行赋值语句async
let first = 4
复制代码
而后执行到下个yield
为止,即函数
yield first + 2 // 4 + 2
复制代码
最后返回 { value: 6, done: false }
以后的 next
依上面的原理而执行,直到迭代完毕。
也就是说,经过next
的参数,generator
产生的 iterator
,与外部环境搭建起了沟通的桥梁,结合 iterator
能够停顿的特色,能够作一些有意思的事,如用同步方式写回调等,详见下文。
iterator
里抛错function *createIterator() {
let first = yield 1;
let second = yield first + 2; // yield 4 + 2, 而后抛出错误
yield second + 3; // 不会被执行
}
let iterator = createIterator();
console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next(4)); // {value: 6, done: false}
console.log(iterator.throw(new Error("Boom"))); // generator 里抛出的错误
复制代码
根据上面说的执行机制,这里例子的执行流程能够用这张图表示:
第三次执行迭代时,咱们调用 iterator.throw(new Error("Boom"))
, 向 iterator
里抛出错误,传入的参数为错误信息。
咱们能够改造 createIterator
以下:
function* createIterator() {
let first = yield 1;
let second;
try {
second = yield first + 2;
} catch (ex) {
second = 6;
}
yield second + 3;
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next(4)); // "{ value: 6, done: false }"
console.log(iterator.throw(new Error("Boom"))); // "{ value: 9, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
复制代码
其执行流程解释以下:
前两次调用 next
状况和上面执行机制里的分析是同样的,就不赘述了。
第三次调用 iterator.throw(new Error("Boom")
往generator
往抛入错误,函数内部在上次中止处即 yield first + 2
接收信息,抛出错误。可是被catch
了,因此继续执行到下一个停顿点:
yield second + 3; // 6 + 3
复制代码
最后返回本次迭代结果 { value: 9, done: false }
继续执行其余迭代,和上没无甚不一样,不赘述。
小结: 这里有能够看到,
next()
和throw()
均可以让iterator
继续执行下去,不一样的是后者会是以抛出错误的方式让iterator
继续执行的。但在这以后,generator
里会发生什么,取决于代码怎么写的了。
Generator
里的 return
语句这里的 return
语句, 功能上与通常函数的 return
没太大区别,都会阻止 return
以后的语句执行。
function* createIterator() {
yield 1;
return;
yield 2;
yield 3;
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
复制代码
上面的 return
, 使得以后的 yield
都被忽略了,因此,迭代二次而卒。
可是,若是 return
后有值,会被计入本次迭代的结果中:
function* createIterator() {
yield 1;
return 42;
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 42, done: true }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
复制代码
这个iterator
执行两次就可收摊了,和上一个例子不一样的是,最后一次返回结果里有 return
后的值 { value: 42, done: true }
。
又可是,这个返回值只能用一次,因此第三次执行next
, 返回结果变成了 { value: undefined, done: true }
。
特别注意: 展开操做符...
和 for-of
看到迭代结果里 done
是 true
就立刻中止执行,连 return
后面的值也无论了,中止得很决绝。如上面的例子,用for-of
和 ...
执行:
function* createIterator() {
yield 1;
return 42;
}
let iterator = createIterator();
for(let item of iterator) {
console.log(item);
}
// 1
let anotherIterator = createIterator();
console.log([...anotherIterator])
// [1]
// 猜猜 [...iterator] 的结果是什么
复制代码
generator
委托是什么,简单说就是把 generator
A 委托给 generator
B, 让 B 代为执行:
function* createNumberIterator() {
yield 1;
yield 2;
}
function* createColorIterator() {
yield "red";
yield "green";
}
function* createCombinedIterator() {
yield* createNumberIterator();
yield* createColorIterator();
yield true;
}
var iterator = createCombinedIterator();
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: "green", done: false }"
console.log(iterator.next()); // "{ value: true, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
复制代码
以上可见,委托的语法,就是在一个 generator
里, 用 yield*
操做另外一个 generator
的执行结果。
经过委托把不一样的 generator
放一块儿,再利用return
的返回值,能够在 generator
里通讯,给出了更多的想象空间:
function* createNumberIterator() {
yield 1;
yield 2;
return 3;
}
function* createRepeatingIterator(count) {
for (let i = 0; i < count; i++) {
yield "repeat";
}
}
function* createCombinedIterator() {
let result = yield* createNumberIterator();
yield* createRepeatingIterator(result);
}
var iterator = createCombinedIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
复制代码
如上, createNumberIterator
的返回值 3
传入了createRepeatingIterator
里, 若是拆开写,是这样:
function* createNumberIterator() {
yield 1;
yield 2;
return 3;
}
function* createRepeatingIterator(count) {
for (let i = 0; i < count; i++) {
yield "repeat";
}
}
function* createCombinedIterator() {
let result = yield* createNumberIterator();
yield result;
yield* createRepeatingIterator(result);
}
var iterator = createCombinedIterator();
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: "repeat", done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
复制代码
注意:既然
yield *
后面接的是generator
的执行结果,而generator
是iterable
。就是说,yield *
后能够直接跟iterable
, 如字符串。如:
let g = function *() {
yield *['a', 'b', 'c']
}
for(let item of g()) {
console.log(item);
}
// a
// b
// c
复制代码
Genarator
与异步关于 js
里异步的特色,这里展开说了。简单来说,它让 js
这们单线程语言更强大; 可是,异步状况一复杂好比有异步之间有依赖,那就很容易写出以下的callback hell
, 极难维护:
合理利用 genarator
就能够用同步的写法,写异步。
从以前的介绍里已经知道,genarator
返回 iterator
, 须要手动调用 next
, 很麻烦。那若是封装一些,可让 iterator
本身执行完毕,不就很好了:
前期准备,实现自动执行 generator
的函数
run(function* () {
let value = yield 1;
console.log(value);
value = yield value + 3;
console.log(value);
});
复制代码
要让它本身执行,那么 run
须要:
generator
, 拿到 iterator
;iterator.next()
;iterator.next(lastResult)
参数,继续迭代;实现以下:
function run(taskDef) {
// 建立并保存 iterator,留到后面使用
let task = taskDef();
let result = task.next();
// 递归地执行 `next`
function step() {
// 若是没完的话
if (!result.done) {
result = task.next(result.value);
step();
}
}
// 开始处理
step();
}
复制代码
实现目标,用同步方式写异步
加入咱们要让下面这段代码可行:
const asyncWork = new Promise((resolve, reject) => {
setTimeout(() => resolve(5), 500)
})
run(function* () {
let value = yield asyncWork;
console.log(value)
value = yield value + 3;
console.log(value)
});
复制代码
这里和上一个例子不一样的地方在于,yield
返回结果多是个promise
, 那咱们加个判断就能够了:
if (result.value && typeof result.value.then === 'function') {
result.value.then(d => {
result = task.next(d)
...
})
}
复制代码
就是判断若是是 promise
, 执行 then
函数,把返回结果传入下一次迭代 next(d)
便可。完整示例代码以下:
function run(taskDef) {
// 建立并保存 iterator,留到后面使用
let task = taskDef();
let result = task.next();
// 递归地执行 `next`
function step() {
// 若是没完的话
if (!result.done) {
if (result.value && typeof result.value.then === 'function') {
result.value.then(d => {
result = task.next(d)
step();
})
} else {
result = task.next(result.value);
step();
}
}
}
// 开始处理
step();
}
复制代码
回头看看这个写法:
run(function* () {
let value = yield asyncWork;
console.log(value)
value = yield value + 3;
console.log(value)
});
复制代码
虽然第二个 yield
对上一个 yield
结果有依赖,但不用写成回调,看着跟同步同样,很直白!
generator
产生的 iterator
, 能够用next
,在函数外部往 generator
里传数据, 又能够经过 throw
往里抛错。它们至关于在 generator
里对外打开了多个通讯窗口,这让清晰的异步成为可能。强大的 redux-saga
也是基于 generator
实现的。是否是有更多的玩法?一切都是抛砖引玉,不知道你们还有其余玩法没?
若是对 generator
由来不太清楚的,也能够先看看 这里
另外,这篇文章最早发布在 github,是个关于 ES6
的系列文章。若是以为能够,帮忙 star
下呗,方便找工做啊。哎,找工做,真-是-累-啊!!!