前言
ES6 中提出一个叫生成器 (Generator) 的概念, 执行生成器函数, 会返回迭代器对象(Iterator), 这个迭代器对象可以遍历函数内部的每一个状态.
- function* helloWorldGenerator() {
- yield 'hello';
- yield 'world';
- return 'ending';
- }
- // 通过执行生成器返回迭代器对象
- var helloWorldIterator = helloWorldGenerator();
- helloWorldIterator.next();
- // { value: "hello", done: false }
- helloWorldIterator.next();
- // { value: "world", done: false }
- helloWorldIterator.next();
- // { value: "ending", done: true }
- helloWorldIterator.next();
- // { value: undefined, done: true }
迭代器对象通过调用 next() 方法, 遍历下一个内部状态, 生成一个值, 这也是 Generator 名字的由来.
一, generator 的异步调用
每当 generator 生成一个值, 程序会挂起, 自动停止执行, 随后等待下一次执行, 直到下一次调用 next() 方法, 但并不影响外部主线程其他函数的执行.
generator 让函数执行过程有了同步的特点, 基于这个特点, 我们将异步调用和生成器结合起来:
先后打印 "hello China!", "hello Wolrd!", "hello Earth!";
- 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() 方法了.
- 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 方法捕捉到错误, 从而增强了异步编程中从错误处理能力.
- 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); // 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 则能将异步编程写得更简洁优雅.
来源: https://juejin.im/post/5c7c7e7f6fb9a049a8200ba4