es6之异步操作和Async函数
基本概念
异步:
所谓“异步”,简单说就是一个任务分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段。
比如,有一个任务是读取文件进行处理,任务的第一段是向操作系统发出请求,要求读取文件。然后,程序执行其他任务,等到操作系统返回文件,再接着执行任务的第二段(处理文件)。这种不连续的执行,就叫做异步。
相应的,连续的执行就叫做同步。由于是连续执行,不能插入其他任务,所以操作系统从硬盘读取文件的这段时间,程序只能干等着。
回调函数
Javascript语言对异步编程呢个的实现,就是回调函数。所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。他的英语名字callback,直译过来就是“重新调用”。
fs.readFile('/etc/passwd',function(err,data){
if(err) throw err;
console.log(data);
})
上面代码中,readFile函数的第二个参数,就是回调函数,也就是任务的第二段。等到操作系统返回了/etc/passwd这个文件以后,回调函数才会执行。
一个有趣的问题是,为什么Node.js约定,回调函数的一个参数,必须是错误对象err(如果没有错误,该参数就是null)?原因是执行分成两段,在这两段之间抛出的错误,程序无法捕捉,只能当作参数,传入第二段。
Promise
回调函数本身并没有问题,他的问题出现在多个回调函数嵌套。假定读取A文件之后,再读取B文件,代码如下。
fs.readFile(fileA,function(err,data){
fs.readFile(fileB,function(err,data){
// ...
})
})
不难想象,如果依次读取多个文件,就会出现多重嵌套。代码不是纵向发展,而是横向发展,很快就会乱成一团,无法管理。这种情况就成为“回调函数噩梦”(callback hell)。
Promise就是为了解决这个问题而提出的。他不是新的语法功能,而是一种新的写法,允许将回调函数的横向加载,改成纵向加载。采用Promise,连续读取多个文件,写法如下。
var readFile = require('fs-readfile-promise');
readFile(fileA)
.then(function(data){
console.log(data.toString());
})
.then(function(){
return readFile(fileB);
})
.then(function(data){
console.log(data.toString());
})
.catch(function(err){
console.log(err);
})
async函数
async函数就是Generator函数的语法糖。(async await)
async函数对Generator函数的改进,体现在以下三点。
- 内置执行器。Genarator函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。
- 更好的语义。async和await,比起星号和yield,语义更清楚了。async表示函数里由异步操作,await表示紧跟在后面的表达式需要等待结果。
- 更广的适用性。co模块约定,yield命令后面只能是Thunk函数或Promise对象,而async函数的await命令后面,可以是Promise对像和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
- 返回值是Promise。async函数的返回值是Promise对象,这比Generator函数的返回值是Tterator对象方便多了。你可以用then方法指定下一步的操作。
进一步说,async函数完全可以看作多个异步操作,包装成的一个Promise对象,而await命令就是内部then命令的语法糖。
async函数的用法:
注意点:
await命令后面的Promise对像,运行结果可能是rejected,所以最好把await命令放在try…catch代码块中。
async function myFunction(){
try {
await somethingThatReturnsAPromise();
} catch (err){
console.log(err);
}
}
// 另一种写法
async function myFunction(){
await somethingThatReturnsAPromise().catch (err){
console.log(err);
}
}
await命令只能在async函数之中,如果用在普通函数,就会报错。