Generator是什么
Generator 函数是 ES6 提供的一种异步编程解决方案。 我们之前解决异步编程的通用方案有回调函数和promise。其中promsie
已经是一种比较流行的解决异步方案,那么为什么还出现Generator
?甚至async/await
呢?该问题我们留在后面再进行分析,下面先认识下Generator
。
Generator函数
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
Generator
函数是使用function*
语法定义的函数,其内部可以包含yield
关键字。yield
关键字用于暂停函数的执行并返回一个值给调用者,同时保存了函数的执行状态。 下次调用Generator
函数时,它会从上次暂停的地方继续执行。
执行 Generator
函数会返回一个遍历器对象,可以依次遍历 Generator
函数内部的每一个状态。
Generator函数的使用
Generator
函数会返回一个遍历器对象,即具有Symbol.iterator
属性,并且返回给自己。
function* gen(){
// 一些代码
}
var g = gen();
g[Symbol.iterator]() === g
// true
通过yield
关键字可以暂停generator
函数返回的遍历器对象的状态。
function* Gen() {
yield 'hello';
yield 'world';
return 'ending';
}
上述存在三个状态:hello
、world
、return
,通过next
方法才会遍历到下一个内部状态, 其运行逻辑如下:
- 遇到
yield
表达式,就暂停执行后面的操作,并将紧跟在yield
后面的那个表达式的值,作为返回的对象的value
属性值。 - 下一次调用
next
方法时,再继续往下执行,直到遇到下一个yield
表达式 - 如果没有再遇到新的
yield
表达式,就一直运行到函数结束,直到return
语句为止,并将return
语句后面的表达式的值,作为返回的对象的value
属性值。 - 如果该函数没有
return
语句,则返回的对象的value
属性值为undefined
Gen.next()
// { value: 'hello', done: false }
Gen.next()
// { value: 'world', done: false }
Gen.next()
// { value: 'ending', done: true }
Gen.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
异步解决方案
异步解决的方案共有以下几种:
- 回调函数
- Promise 对象
- generator 函数
- async/await
回调函数
所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,再调用这个函数。
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 ReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
区别
通过上述代码进行分析,将promise
、Generator
、async/await
进行比较:
promise
和async/await
是专门用于处理异步操作的。Generator
并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署Interator
接口等等)。promise
编写代码相比Generator
、async
更为复杂化,且可读性也稍差。Generator
、async
需要与promise
对象搭配处理异步情况。async
实质是Generator
的语法糖,相当于会自动执行Generator
函数。async
使用上更为简洁,将异步代码以同步的形式进行编写,是处理异步编程的最终方案。
总结
Generator
是异步解决的一种方案,最大特点则是将异步操作同步化表达出来,使代码可读性变高 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);
}