async await

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的生成器函数总结有四点:



  1. yield必须放置在*函数中;

  2. 每次执行到yield时都会暂停函数中剩余代码的执行;

  3. *函数必须通过函数调用的方式(new方式会报错)才能产生自身的实例,并且每个实例都互相独立;

  4. 一个生成器函数一旦迭代完成,则再也无法还原,一直停留在最后一个位置;



https://blog.csdn.net/yiifaa/article/details/77872086


Category node