await处理异步的时候,每执行到await就会跳出其所在的async函数,等到await有了结果(resolve或者reject)才会回到async函数继续执行。也就是说async内的await使继发的,那么如何用await实现并发呢?
例如我想读取两个文件,由于这两个文件之间没有依赖关系,我想并发读取
var fs=require('fs');
var read=function (path) {
return new Promise((resolve,reject)=>{
fs.readFile(path,(err,data)=>{
if(err){
reject(err)
}else{
resolve(data)
}
})
})
}
//继发读取
async function ReadTwo(){
var f1=await read('./a.txt');
var f2=await read('./b.txt');
}
第一种办法使利用Promise.all
async function readTwo{
var p=Promise.all([read('./a.txt'),read('./b.txt')]);
var [f1,f2]=await p
}
第二种办法
async function readTwo{
var p1=read('./a.txt');
var p2=read('./a.txt');
f1=await p1;
f2=await p2;
}
为什么这样就是并发呢?明明还是两个await啊?
因为readFile本身是非阻塞的!p1=read(’./a.txt’)是把一个promise对象赋给p1,不会停在这里等待他的执行结果,而p1=await read(’./a.txt’)是把promise的执行结果赋给p1,在promise有结果之前不会执行赋值操作。
async内部代码在promise返回结果之前会阻塞住,但整个主线程不会阻塞,而是会执行async函数后面的同步代码。等promise有了结果且外面的同步代码执行完了就又会回到async函数原来阻塞的地方继续执行。
分析:
var p1=read('./a.txt')
这一行会把读取a.txt的任务交给文件读取模块(nodejs的异步I/O),然后立即执行下一句var p2=(’./b.txt’),立即把读取b.txt的任务交给文件读取模块 ,这时我们的计算机就同时在读取a.txt和b.txt啦。后面的await跟继发一样在等待文件读取的结果,不同的是,这次两个文件同时在读取。也就是说读取文件是并行的,只不过读取结果的赋值是继发的。
补充:数组的map,foreach等方法的参数函数是async的情况
这个例子出自阮一峰老师《ES6入门》,目的是实现并发获取一组url的数据,然后将获取到的结果顺序输出。
async function logInOrder(urls) {
// 并发读取远程URL
const textPromises = urls.map(async url => {
const response = await fetch(url);
return response.text();
});
// 按次序输出
for (const textPromise of textPromises) {
console.log(await textPromise);
}
}
map方法本身不是async的,map的函数参数是async 的。
map可以看作一个循环,下一个循环并不会等待上一个循环有了结果才去执行,每个循环执行到await时,会把远程获取数据的任务交给http模块,然后立即执行下一次循环。至于每个循环体内部未执行完的语句,哪个url先得到结果就先执行哪个。也就是说,内层的await只会阻塞内层,不会阻塞外层。
由于map内部的await不会阻塞map外部代码,那么当url还没有返回结果的时候,就已经执行到了下面的for语句,所以在输出textPromise时需要await,这样可以保证获取到的数据是按照原来url数列的顺序输出的。