一、介绍
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同
除了Generator解决异步的手段还有:
- 回调函数
- promise
promsie已经是一种比较流行的解决异步方案,那么为什么还出现Generator?甚至async/await呢?
这个留在后面再进行分析,下面先认识下Generator
Generator 函数
执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态
形式上,Generator 函数是一个普通函数,但是有两个特征:
function关键字与函数名之间有一个星号- 函数体内部使用
yield表达式,定义不同的内部状态
function* helloWorldGenerator() {
yield "hello";
yield "world";
return "ending";
}
二、使用
Generator 函数会返回一个遍历器对象,即具有Symbol.iterator属性,并且返回给自己
function* gen(){}
var g = gen();
console.log(g[Symbol.iterator]() === g)
// true
通过yield关键字可以暂停generator函数返回的遍历器对象的状态
function* helloWorldGenerator() {
yield "hello";
yield "world";
return "ending";
}
var hw = helloWorldGenerator();
上述存在三个状态:hello、world、ending
通过next方法才会遍历到下一个内部状态,其运行逻辑如下:
- 遇到
yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。 - 下一次调用
next方法时,再继续往下执行,直到遇到下一个yield表达式 - 如果没有再遇到新的
yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。 - 如果该函数没有
return语句,则返回的对象的value属性值为undefined
console.log(hw.next())
// { value: 'hello', done: false }
console.log(hw.next())
// { value: 'world', done: false }
console.log(hw.next())
// { value: 'ending', done: true }
console.log(hw.next())
// { value: undefined, done: true }
done用来判断是否存在下个状态,value对应状态值
并且Generator 函数返回Iterator对象,因此我们还可以通过for...of进行遍历,它会遍历所有done为false的状态值,也就是跟在yield后面表达式的值
for (const v of hw) {
console.log(v); // hello world
}
yield表达式本身没有返回值,或者说总是返回undefined
通过调用next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
function* foo(x) {
var y = 2 * (yield x + 1); // 此处跟在yield后面表达式的值只会返回给next()调用结果,并不会作为(yield x + 1)的返回值
var z = yield y / 3;
return x + y + z;
}
var a = foo(5);
console.log(a.next()); // {value:6, done:false}
console.log(a.next()); // {value:NaN, done:false}
console.log(a.next()); // {value:NaN, done:true}
var b = foo(5);
console.log(b.next()); // { value:6, done:false }
console.log(b.next(12)); // { value:8, done:false }
console.log(b.next(13)); // { value:42, done:true }
三、异步解决方案
目前js中存在的异步解决方案如下:
- 回调函数
- Promise
- Generator
- async/await
这里通过文件读取案例,将几种解决异步的方案进行一个比较:const fs = require("fs");
回调函数
所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,再调用这个函数
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这个文件以后,回调函数才会执行
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* () {
yield readFile("/etc/fstab");
yield readFile("/etc/shells");
};
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、Generator、async/await进行比较:
promise和async/await是专门用于处理异步操作的Generator并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署Interator接口...)promise编写代码相比Generator、async更为复杂化,且可读性也稍差Generator、async需要与promise对象搭配处理异步情况async实质是Generator的语法糖,相当于会自动执行Generator函数async使用上更为简洁,将异步代码以同步的形式进行编写,是处理异步编程的最终方案