async 函数是什么?一句话,它就是 Generator 函数的语法糖。
前文有一个 Generator 函数,依次读取两个文件。
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);
});
});
};
const gen = function* () {
const f1 = yield readFile(‘/etc/fstab’);
const f2 = yield readFile(‘/etc/shells’);
console.log(f1.toString());
console.log(f2.toString());
};
上面代码的函数gen可以写成async函数,就是下面这样。
const asyncReadFile = async function () {
const f1 = await readFile(‘/etc/fstab’);
const f2 = await readFile(‘/etc/shells’);
console.log(f1.toString());
console.log(f2.toString());
};
一比较就会发现,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。
async函数对 Generator 函数的改进,体现在以下四点。
(1)内置执行器。
Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。
asyncReadFile();
上面的代码调用了asyncReadFile函数,然后它就会自动执行,输出最后结果。这完全不像 Generator 函数,需要调用next方法,或者用co模块,才能真正执行,得到最后结果。
(2)更好的语义。
async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
(3)更广的适用性。
co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
(4)返回值是 Promise。
async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。
进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。
https://es6.ruanyifeng.com/#docs/async
二、回调函数的概念
JavaScript 语言对异步编程的实现,就是回调函数。所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。它的英语名字 callback,直译过来就是”重新调用”。
读取文件进行处理,是这样写的。
fs.readFile(‘/etc/passwd’, function (err, data) {
if (err) throw err;
console.log(data);
});
上面代码中,readFile 函数的第二个参数,就是回调函数,也就是任务的第二段。等到操作系统返回了 /etc/passwd 这个文件以后,回调函数才会执行。
一个有趣的问题是,为什么 Node.js 约定,回调函数的第一个参数,必须是错误对象err(如果没有错误,该参数就是 null)?原因是执行分成两段,在这两段之间抛出的错误,程序无法捕捉,只能当作参数,传入第二段。
不难想象,如果依次读取多个文件,就会出现多重嵌套。代码不是纵向发展,而是横向发展,很快就会乱成一团,无法管理。这种情况就称为”回调函数噩梦”(callback hell)。
Promise就是为了解决这个问题而提出的。它不是新的语法功能,而是一种新的写法,允许将回调函数的横向加载,改成纵向加载。采用Promise,连续读取多个文件
四、协程
传统的编程语言,早有异步编程的解决方案(其实是多任务的解决方案)。其中有一种叫做”协程”(coroutine),意思是多个线程互相协作,完成异步任务。
协程有点像函数,又有点像线程。它的运行流程大致如下。
第一步,协程A开始执行。
第二步,协程A执行到一半,进入暂停,执行权转移到协程B。
第三步,(一段时间后)协程B交还执行权。
第四步,协程A恢复执行。
上面流程的协程A,就是异步任务,因为它分成两段(或多段)执行。
举例来说,读取文件的协程写法如下。
function asnycJob() {
// …其他代码
var f = yield readFile(fileA);
// …其他代码
}
上面代码的函数 asyncJob 是一个协程,它的奥妙就在其中的 yield 命令。它表示执行到此处,执行权将交给其他协程。也就是说,yield命令是异步两个阶段的分界线。
协程遇到 yield 命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。它的最大优点,就是代码的写法非常像同步操作,如果去除yield命令,简直一模一样。
五、Generator函数的概念
Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。
function* gen(x){
var y = yield x + 2;
return y;
}
上面代码就是一个 Generator 函数。它不同于普通函数,是可以暂停执行的,所以函数名之前要加星号,以示区别。
整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用 yield 语句注明。Generator 函数的执行方法如下。
var g = gen(1);
g.next() // { value: 3, done: false }
g.next() // { value: undefined, done: true }
上面代码中,调用 Generator 函数,会返回一个内部指针(即遍历器 )g 。这是 Generator 函数不同于普通函数的另一个地方,即执行它不会返回结果,返回的是指针对象。调用指针 g 的 next 方法,会移动内部指针(即执行异步任务的第一段),指向第一个遇到的 yield 语句,上例是执行到 x + 2 为止。
换言之,next 方法的作用是分阶段执行 Generator 函数。每次调用 next 方法,会返回一个对象,表示当前阶段的信息( value 属性和 done 属性)。value 属性是 yield 语句后面表达式的值,表示当前阶段的值;done 属性是一个布尔值,表示 Generator 函数是否执行完毕,即是否还有下一个阶段。
http://www.ruanyifeng.com/blog/2015/04/generator.html
二、Thunk 函数的含义
编译器的”传名调用”实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体。这个临时函数就叫做 Thunk 函数。
function f(m){
return m * 2;
}
f(x + 5);
// 等同于
var thunk = function () {
return x + 5;
};
function f(thunk){
return thunk() * 2;
}
上面代码中,函数 f 的参数 x + 5 被一个函数替换了。凡是用到原参数的地方,对 Thunk 函数求值即可。
这就是 Thunk 函数的定义,它是”传名调用”的一种实现策略,用来替换某个表达式。
http://www.ruanyifeng.com/blog/2015/05/thunk.html
有一个 Generator 函数,用于依次读取两个文件。
var gen = function* (){
var f1 = yield readFile(‘/etc/fstab’);
var f2 = yield readFile(‘/etc/shells’);
console.log(f1.toString());
console.log(f2.toString());
};
co 函数库可以让你不用编写 Generator 函数的执行器。
var co = require(‘co’);
co(gen);
上面代码中,Generator 函数只要传入 co 函数,就会自动执行。
co 函数返回一个 Promise 对象,因此可以用 then 方法添加回调函数。
co(gen).then(function (){
console.log(‘Generator 函数执行完成’);
})
上面代码中,等到 Generator 函数执行结束,就会输出一行提示。
http://www.ruanyifeng.com/blog/2015/05/co.html
http://www.ruanyifeng.com/blog/2015/05/async.html
https://www.zhihu.com/question/32752866
对于ES6的生成器函数总结有四点:
https://blog.csdn.net/yiifaa/article/details/77872086