一 怎么理解ES6中Generator的?

1.1 介绍

Generator 函数是 ES6 提供的⼀种异步编程解决⽅案,语法⾏为与传统函数完全不同

回顾下上⽂提到的解决异步的⼿段:

  • 回调函数
  • promise

那么,上⽂我们提到 promsie 已经是⼀种⽐较流⾏的解决异步⽅案,那么为什么还出现Generator?甚⾄ async/await 呢?

该问题我们留在后⾯再进⾏分析,下⾯先认识下 Generator

1.1.1 Generator函数

执⾏ Generator 函数会返回⼀个遍历器对象,可以依次遍历 Generator 函数内部的每⼀个状态形式上, Generator 函数是⼀个普通函数,但是有两个特征:

  • function 关键字与函数名之间有⼀个星号
  • 函数体内部使⽤ yield 表达式,定义不同的内部状态
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}

1.2 使⽤

Generator 函数会返回⼀个遍历器对象,即具有 Symbol.iterator 属性,并且返回给⾃⼰

function* gen(){
// some code
}
var g = gen();
g[Symbol.iterator]() === g
// true

通过 yield 关键字可以暂停 generator 函数返回的遍历器对象的状态

function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();

上述存在三个状态: hello 、 world 、 return

通过 next ⽅法才会遍历到下⼀个内部状态,其运⾏逻辑如下:

  • 遇到 yield 表达式,就暂停执⾏后⾯的操作,并将紧跟在 yield 后⾯的那个表达式的值,作为返回的对象的 value 属性值。
  • 下⼀次调⽤ next ⽅法时,再继续往下执⾏,直到遇到下⼀个 yield 表达式
  • 如果没有再遇到新的 yield 表达式,就⼀直运⾏到函数结束,直到 return 语句为⽌,并将return 语句后⾯的表达式的值,作为返回的对象的 value 属性值。
  • 如果该函数没有 return 语句,则返回的对象的 value 属性值为 undefined
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }

done ⽤来判断是否存在下个状态, value 对应状态值

yield 表达式本⾝没有返回值,或者说总是返回 undefined

通过调⽤ next ⽅法可以带⼀个参数,该参数就会被当作上⼀个 yield 表达式的返回值

function* foo(x) {
 var y = 2 * (yield (x + 1));
 var z = yield (y / 3);
 return (x + y + z);
 }
 var a = foo(5);
 a.next() // Object{value:6, done:false}
 a.next() // Object{value:NaN, done:false}
 a.next() // Object{value:NaN, done:true}
 var b = foo(5);
 b.next() // { value:6, done:false }
 b.next(12) // { value:8, done:false }
 b.next(13) // { value:42, done:true }

正因为 Generator 函数返回 Iterator 对象,因此我们还可以通过 for...of 进⾏遍历

function* foo() {
 yield 1;
 yield 2;
 yield 3;
 yield 4;
 yield 5;
 return 6;
 }
 for (let v of foo()) {
 console.log(v);
 }
 // 1 2 3 4 5

原⽣对象没有遍历接⼝,通过 Generator 函数为它加上这个接⼝,就能使⽤ for...of 进⾏遍历了

function* objectEntries(obj) {
 let propKeys = Reflect.ownKeys(obj);
 for (let propKey of propKeys) {
 yield [propKey, obj[propKey]];
 }
 }
 let jane = { first: 'Jane', last: 'Doe' };
 for (let [key, value] of objectEntries(jane)) {
 console.log(
 ${key}: ${value}
 );
 }
 // first: Jane
 // last: Doe

1.3 异步解决⽅案

回顾之前展开异步解决的⽅案:

  • 回调函数
  • Promise 对象
  • generator 函数
  • async/await

这⾥通过⽂件读取案例,将⼏种解决异步的⽅案进⾏⼀个⽐较:

1.3.1 回调函数

所谓回调函数,就是把任务的第⼆段单独写在⼀个函数⾥⾯,等到重新执⾏这个任务的时候,再调⽤这个函数

fs.readFile('/etc/fstab', function (err, data) {
 if (err) throw err;
 console.log(data);
 fs.readFile('/etc/shells', function (err, data) {
 if (err) throw err;
 console.log(data);
 });
 });

readFile 函数的第三个参数,就是回调函数,等到操作系统返回了 /etc/passwd 这个⽂件以后,回调函数才会执⾏

1.3.2 Promise

Promise 就是为了解决回调地狱⽽产⽣的,将回调函数的嵌套,改成链式调⽤

const fs = require('fs');
 const readFile = function (fileName) {
 return new Promise(function (resolve, reject) {
 fs.readFile(fileName, function(error, data) {
 if (error) return reject(error);
 resolve(data);
 });
 });
 };
 readFile('/etc/fstab').then(data =>{
 console.log(data)
 return readFile('/etc/shells')
 }).then(data => {
 console.log(data)
 })

这种链式操作形式,使异步任务的两段执⾏更清楚了,但是也存在了很明显的问题,代码变得冗杂了,语义化并不强

1.3.3 generator

yield 表达式可以暂停函数执⾏, next ⽅法⽤于恢复函数执⾏,这使得 Generator 函数⾮常适合将异步任务同步化

const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};

1.3.4 async/await

将上⾯ Generator 函数改成 async/await 形式,更为简洁,语义化更强了

const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};

1.3.5 区别

通过上述代码进⾏分析,将 promise 、 Generator 、 async/await 进⾏⽐较:

  • promise 和 async/await 是专⻔⽤于处理异步操作的
  • Generator 并不是为异步⽽设计出来的,它还有其他功能(对象迭代、控制输出、部署Interator 接⼝...)
  • promise 编写代码相⽐ Generator 、 async 更为复杂化,且可读性也稍差
  • Generator 、 async 需要与 promise 对象搭配处理异步情况
  • async 实质是 Generator 的语法糖,相当于会⾃动执⾏ Generator 函数
  • async 使⽤上更为简洁,将异步代码以同步的形式进⾏编写,是处理异步编程的最终⽅案

1.4 使⽤场景

Generator 是异步解决的⼀种⽅案,最⼤特点则是将异步操作同步化表达出来

function* loadUI() {
showLoadingScreen();
yield loadUIDataAsynchronously();
hideLoadingScreen();
}
var loader = loadUI();
// 加载UI
loader.next()
// 卸载UI
loader.next()

包括 redux-saga 中间件也充分利⽤了 Generator 特性

import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
 import Api from '...'
 function* fetchUser(action) {
 try {
 const user = yield call(Api.fetchUser, action.payload.userId);
 yield put({type: "USER_FETCH_SUCCEEDED", user: user});
 } catch (e) {
 yield put({type: "USER_FETCH_FAILED", message: e.message});
 }
 }
 function* mySaga() {
 yield takeEvery("USER_FETCH_REQUESTED", fetchUser);
 }
 function* mySaga() {
 yield takeLatest("USER_FETCH_REQUESTED", fetchUser);
 }
 export default mySaga;

还能利⽤ Generator 函数,在对象上实现 Iterator 接⼝

function* iterEntries(obj) {
let keys = Object.keys(obj);
for (let i=0; i < keys.length; i++) {
let key = keys[i];
yield [key, obj[key]];
}
}
let myObj = { foo: 3, bar: 7 };
for (let [key, value] of iterEntries(myObj)) {
console.log(key, value);
}
// foo 3
// bar 7