前言
ES6 中提出一个叫生成器(Generator)的概念,执行生成器函数,会返回迭代器对象(Iterator),这个迭代器对象可以遍历函数内部的每一个状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function* helloWorldGenerator() { yield "hello"; yield "world"; return "ending"; }
var helloWorldIterator = helloWorldGenerator();
helloWorldIterator.next();
helloWorldIterator.next();
helloWorldIterator.next();
helloWorldIterator.next();
|
迭代器对象通过调用 next() 方法,遍历下一个内部状态,生成一个值,这也是 Generator 名字的由来。
一、generator 的异步调用
每当 generator 生成一个值,程序会挂起,自动停止执行,随后等待下一次执行,直到下一次调用 next() 方法,但并不影响外部主线程其他函数的执行。
generator 让函数执行过程有了同步的特点,基于这个特点,我们将异步调用和生成器结合起来:
先后打印 “hello China!”, “hello Wolrd!”, “hello Earth!”;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| function fetch(word) { return new Promise((resolve, reject) => { setTimeout(() => { resolve("hello " + word); }, 2000); }); }
function* gen() { try { const api1 = yield fetch("China!"); console.log(1);
const api2 = yield fetch("World!"); console.log(2);
const api3 = yield fetch("Earth!"); console.log(3); } catch (error) { console.log(error); } }
const iterator = gen();
const result1 = iterator.next().value;
result1 .then(res1 => { console.log(res1); return iterator.next().value; }) .then(res2 => { console.log(res2); return iterator.next().value; }) .then(res3 => { console.log(res3); return iterator.next().value; });
|
每次调用迭代器的 next 方法,会返回一个 Promise 对象,通过 Promise 对象状态从 pending 转移到 fullfilled 状态,可以在 .then() 方法后执行下一个异步方法。
二、Generator 自执行
从第二节中可以看出,Generator 每次调用异步方法,都要手动执行一次 iterator.next(),通过递归 iterator.next() 我们就不用再手动执行 next() 方法了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| function fetch(word) { return new Promise((resolve, reject) => { setTimeout(() => { resolve("hello " + word); }, 2000); }); }
function* gen() { try { const api1 = yield fetch("China!"); console.log(1);
const api2 = yield fetch("World!"); console.log(2);
const api3 = yield fetch("Earth!"); console.log(3); } catch (error) { console.log(error); } }
function co(gen) { const g = gen();
function next(data) { const result = g.next(data); if (result.done) return; result.value.then(data => { console.log(data); next(data); }); }
next(); }
co(gen);
|
三、在迭代器中抛出错误
迭代器除了能在 next() 方法中传递参数外,还能通过 iterator.throw 方法捕捉到错误,从而增强了异步编程中从错误处理能力。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| function fetch(word) { return new Promise((resolve, reject) => { setTimeout(() => { resolve("hello " + word); }, 2000); }); }
function* gen() { try { const api1 = yield fetch("China!"); console.log(1);
const api2 = yield fetch("World!"); console.log(2);
const api3 = yield fetch("Earth!"); console.log(3); } catch (error) { console.log(error); } }
const iterator = gen();
const result1 = iterator.next().value;
result1 .then(res1 => { console.log(res1); iterator.throw(new Error("抛出一个错误")); return iterator.next().value; }) .then(res2 => { console.log(res2); return iterator.next().value; }) .then(res3 => { console.log(res3); return iterator.next().value; });
|
调用了 iterator.throw 方法后,错误就能被抛出被生成器中的中的 try catch 捕捉到,且阻止后面的代码继续执行。
四、Generator 应用场景
Generator 最令人兴奋的地方在于,生成器中的异步方法看起来更像是同步方式。不好的地方在于执行过程比较生硬。
总结
Generator 生成具有 Symbol.iterator 属性的迭代器对象,迭代器具有 next 方法,能够无阻塞地将代码挂起,下次调用 .next() 方法再恢复执行。
用 Generator 实现异步编程只是一个 hack 用法,Generator 的语法糖 async & await 则能将异步编程写得更简洁优雅。