基本概念
async
函数是什么?一句话,它就是 Generator 函数的语法糖。
将上一章的代码改成 async 函数的版本:
const { promisify } = require("util");
const path = require('path')
const file1 = path.join(__dirname, './text/1.txt')
const file2 = path.join(__dirname, './text/2.txt')
const readFileP = promisify(readFile)
function* f() {
let data1 = yield readFileP(file1)
console.log('耶,完成了1,数据是' + data1);
let data2 = yield readFileP(file2)
console.log('耶,完成了2,数据是' + data2);
}
//async函数的版本
async function f() {
let data1 = await readFileP(file1)
console.log('耶,完成了1,数据是' + data1);
let data2 = await readFileP(file2)
console.log('耶,完成了2,数据是' + data2);
}
比较后就会发现,async
函数的版本就是将 Generator 函数的星号(*
)替换成async
,将yield
替换成await
。
定义async函数
使用async关键字定义一个async函数:
async function f() {
let data1 = await readFileP(file1)
console.log('耶,完成了1,数据是' + data1);
let data2 = await readFileP(file2)
console.log('耶,完成了2,数据是' + data2);
}
执行async函数
执行async
函数则相当于执行了一个自动运行的Generator函数,async
函数如果返回的结果不是Promise,则会运行结果包装成一个Promise返回:
async function f() {
console.log(1);
}
f().then(()=>{
console.log(2);
})
async function f() {
console.log(1);
return 'done'
}
f().then(value => {
console.log(value);
})
await关键字
与yield
类似,async
函数中可以使用await
关键字,await
关键字后面一般会写一个Promise实例,async
函数执行的过程中,每次遇到await
关键字,会将控制权转回外部环境。
- 如果
await
后面是Promise实例,则会等到该 Promise实例被resolve后,才会把本次await
到下次await
之间的代码推到MircoTask(微任务)
中等待执行,并且await
的返回值是该Promise实例resolve的值 - 如果
await
后面不是Promise实例,则会立即将本次await
到下次await
之间的代码推到MircoTask(微任务)
中等待执行,并且await
的返回值是等于await
后面表达式的值:
async function f() {
let data = await new Promise((resolve, reject) => {
setTimeout(() => {
resolve('a')
}, 2000)
})
console.log(data);
}
//f()
//console.log('end')
如果await
后面不是Promise 实例
async function f() {
let data = await 'a'
console.log(data);
}
f()
console.log('end');
//end
//a
async函数的错误处理
如果Promise被reject或抛出错误,await之后的代码不会执行,因此,需要使用try..catch
对await
进行错误捕捉:
async function f() {
try {
let data = await new Promise((resolve, reject) => {
setTimeout(() => {
reject('123')
}, 2000)
})
//后续代码无法执行
console.log('done');
}catch (e) {
console.log('发生错误:',e);
}
}
f()
async函数处理并发异步任务
如果,async
函数中的每个await
都是等到前面await
resolve后才会执行,如果想并发执行,可以使用Promise.all
:
/*并发处理异步*/
async function f() {
let time1 = new Date()
let [data1,data2] = await Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('123')
}, 2000)
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('123')
}, 3000)
})
])
console.log(data1,data2,'用时:'+ (new Date() - time1));
}
f()
async函数与Promise的对比
用async
函数写异步逻辑相比Promise会更加简洁,在处理不同异步结果相互依赖,错误处理,if…else分支等情况时更加简便:
const {readFile} = require('fs')
const { promisify } = require("util");
const path = require('path')
const file1 = path.join(__dirname, './text/1.txt')
const file2 = path.join(__dirname, './text/2.txt')
const file3 = path.join(__dirname, './text/3.txt')
const readFileP = promisify(readFile)
function f1() {
readFileP(file1).then(data1 =>{
console.log('耶,完成了1,数据是' + data1);
return readFileP(file2)
}).then(data2 => {
console.log('耶,完成了1,数据是' + data2);
return readFileP(file3)
}).then(data3 => {
console.log('耶,完成了1,数据是' + data3);
})
}
async function f2() {
let data1 = await readFileP(file1)
console.log('耶,完成了1,数据是' + data1);
let data2 = await readFileP(file2)
console.log('耶,完成了2,数据是' + data1 + data2);
let data3 = await readFileP(file3)
console.log('耶,完成了2,数据是' + data1 + data2 + data3);
}
f()
ES7 标准中新增的 async
函数,从目前的内部实现来说其实就是 Generator
函数的语法糖。
它基于 Promise,并与所有现存的基于Promise 的 API 兼容。
async 关键字
-
async
关键字用于声明⼀个异步函数(如async function asyncTask1() {...}
) -
async
会⾃动将常规函数转换成 Promise,返回值也是⼀个 Promise 对象 -
async
函数内部可以使⽤await
await 关键字
-
await
用于等待异步的功能执⾏完毕var result = await someAsyncCall()
-
await
放置在 Promise 调⽤之前,会强制async函数中其他代码等待,直到 Promise 完成并返回结果 -
await
只能与 Promise ⼀起使⽤ -
await
只能在async
函数内部使⽤
相较于 Promise,async/await有何优势?
- 同步化代码的阅读体验(Promise 虽然摆脱了回调地狱,但 then 链式调⽤的阅读负担还是存在的)
- 和同步代码更一致的错误处理方式( async/await 可以⽤成熟的 try/catch 做处理,比 Promise 的错误捕获更简洁直观)
- 调试时的阅读性, 也相对更友好
p1.then(()=>{
p2.then(()=>{
p3.then(()=>{})
})
})
await p1()
await p2()
await p3()