Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。
异步编程对 JavaScript 语言太重要。JavaScript 语言的执行环境是“单线程”的,如果没有异步编程,根本没法用,非卡死不可。
ES6 诞生以前,异步编程的方法,大概有下面四种。
- 回调函数
- 事件监听
- 发布/订阅
- Promise 对象
什么是异步?
所谓"异步",简单说就是一个任务不是连续完成的,可以理解成该任务被人为分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段。
比如,有一个任务是读取文件进行处理,任务的第一段是向操作系统发出请求,要求读取文件。然后,程序执行其他任务,等到操作系统返回文件,再接着执行任务的第二段(处理文件)。这种不连续的执行,就叫做异步。
相应地,连续的执行就叫做同步。由于是连续执行,不能插入其他任务,所以操作系统从硬盘读取文件的这段时间,程序只能干等着。
promise解决异步缺点?
promise允许将回调函数的嵌套,改成链式调用。
// 读取文件信息
const fs = require('fs')
const promise = new Promise((resolve, reject) => {
fs.readFile('./01.txt', (err, data) => {
if (err) return reject(err)
resolve(data)
})
})
promise.then(val => {
console.log(val.toString());
return new Promise((resolve, reject) => {
fs.readFile('./02.txt', (err, data) => {
if (err) return reject(err)
resolve(data)
})
})
}).then(val => {
console.log(val.toString());
return new Promise((resolve, reject) => {
fs.readFile('./03.txt', (err, data) => {
if (err) return reject(err)
resolve(data)
})
})
}).then(val => {
console.log(val.toString());
})
// 捕获错误
.catch(err => {
console.log(err);
})
Promise 提供then
方法加载回调函数,catch
方法捕捉执行过程中抛出的错误。
可以看到,Promise 的写法只是回调函数的改进,使用then
方法以后,异步任务的两段执行看得更清楚了,除此以外,并无新意。
Promise 的最大问题是代码冗余,原来的任务被 Promise 包装了一下,不管什么操作,一眼看去都是一堆then
,原来的语义变得很不清楚。
Generator 函数
说到这里我们先需要明白一个概念协程
传统的编程语言,早有异步编程的解决方案(其实是多任务的解决方案)。其中有一种叫做"协程"(coroutine),意思是多个线程互相协作,完成异步任务。
协程有点像函数,又有点像线程。它的运行流程大致如下。
- 第一步,协程
A
开始执行。 - 第二步,协程
A
执行到一半,进入暂停,执行权转移到协程B
。 - 第三步,(一段时间后)协程
B
交还执行权。 - 第四步,协程
A
恢复执行。
上面流程的协程A
,就是异步任务,因为它分成两段(或多段)执行。
function* helloWorld(world) {
var a = yield 'hello' + world
return a
}
const hw = helloWorld('world')
console.log(hw.next()); // {value: 'helloworld', done: false}
// 向内输入一个数据
console.log(hw.next(1)); // {value: undefined, done: true}
next
方法的作用是分阶段执行Generator
函数。每次调用next
方法,会返回一个对象,表示当前阶段的信息(value
属性和done
属性)。
表示当前阶段的值;done
属性是一个布尔值,表示 Generator 函数是否执行完毕,即是否还有下一个阶段。
next
返回值的 value 属性,是 Generator 函数向外输出数据;next
方法还可以接受参数,向 Generator 函数体内输入数据。
异步任务的封装
下面我们通过Generator 函数,来执行一个真实的异步任务。
var fs = require('fs');
var readFile = function (fileName){
return new Promise(function (resolve, reject){
fs.readFile(fileName, function(error, data){
if (error) return reject(error);
resolve(data);
});
});
};
var gen = function* (){
var f1 = yield readFile('01.txt');
var f2 = yield readFile('02.txt');
console.log(f1.toString());
console.log(f2.toString());
};
var g = gen();
g.next().value.then(function(data){
g.next(data).value.then(function(data){
g.next(data);
});
});