一、4条基本概念
- Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同
- 执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态
- Generator 函数定义时 function 关键字与函数名之间有一个星号
Generator 函数体内部使用 yield 表达式,定义不同的内部状态
二、Generator 基本使用
执行 Generator 函数会返回一个遍历器对象,即具有 Symbol.iterator 属性,并且返回给自己function* gen(){}
var g = gen()
g[Symbol.iterator]() === g // true
通过 yield 关键字可以暂停 generator 函数返回的遍历器对象的状态,通过 next 方法才会遍历到下一个内部状态
function* gen(){
yield 'a';
yield 'b';
return 'c';
}
var g = gen()
console.log(g.value) // undefined
console.log(g.next().value) // 'a' 此时 g.next()==={value: 'a', done: false}
代码解析:使用 next 方法时,遇到 yield 表达式,就暂停执行后面的操作,并将紧跟在 yield 后面的那个表达式的值,作为返回的对象的 value 属性值;下一次调用 next 方法时,再继续往下执行,直到遇到下一个 yield 表达式;如果没有再遇到新的 yield 表达式,就一直运行到函数结束,直到 return 语句为止,并将 return 语句后面的表达式的值,作为返回的对象的 value 属性值。如果该函数没有 return 语句,则返回的对象的 value 属性值为 undefined
done 用来判断是否存在下个状态,value 对应状态值
g.next() // { value: 'a', done: false }
g.next() // { value: 'b', done: false }
g.next() // { value: 'c', done: true }
g.next() // { value: undefined, done: true }
yield 表达式本身没有返回值,或者说总是返回 undefined
通过调用 next 方法可以带一个参数,该参数就会被当作上一个 yield 表达式的返回值 ```javascript 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 进行遍历
```javascript
function* gen(){
yield 'a';
yield 'b';
yield 'c';
}
for (let v of gen()) {
console.log(v);
}
// 'a' 'b' 'c'
原生对象没有遍历接口,通过 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
三、异步解决方案对比
回调函数
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);
});
});
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)
})
- 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());
};
- 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());
};
总结:promise 和 async/await 是专门用于处理异步操作的;Generator并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署Interator接口…);promise 编写代码相比Generator、async/await 更为复杂化,且可读性也稍差;Generator、async/await 需要与 promise 对象搭配处理异步情况;async/await 实质是 Generator 的语法糖,相当于会自动执行 Generator 函数;async/await 使用上更为简洁,将异步代码以同步的形式进行编写,是处理异步编程的最终方案
四、Generator 函数使用场景
- 处理异步
- redux-saga 中间件处理 redux 异步操作
- 利用Generator函数,在对象上实现Iterator接口