1. 是什么?
async function 声明创建一个绑定到给定名称的新异步函数。函数体内允许使用 await 关键字,这使得我们可以更简洁地编写基于 promise 的异步代码,并且避免了显式地配置 promise 链的需要。
ES2017 标准引入了 async 函数,使得异步操作变得更加方便。
async 函数是什么?一句话,它就是 Generator 函数的语法糖。
// async 写法
const asyncReadFile = async function () {
const f1 = await readFile('/etc/abc');
const f2 = await readFile('/etc/def');
};
1.1. 和 Generator 函数 对比
- async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await
- Generator 函数的执行必须靠执行器,而async函数自带执行器。即 async函数的执行,与普通函数一样。
// Generator 写法
const gen = function* () {
const f1 = yield readFile('/etc/abc');
const f2 = yield readFile('/etc/def');
};
1.2. 和 Promise 调用 对比
// Promise 写法
const promiseReadFile = function () {
let p1 = new Promise((resolve)=>{
readFile('/etc/abc');
resolve(true)
}).then(v=>console.log(v))
let p2 = new Promise((resolve)=>{
readFile('/etc/def');
})
};
2. 基本用法
2.1. async
async声明的函数返回的是Promise对象,可用then方法添加回调函数。- 只有
async函数内部的异步操作执行完,才会执行then方法指定的回调函数。 async函数内部return语句返回的值,会成为then方法回调函数的参数,若没有return的值,则参数为undefined。
2.2. await
await必须在async声明的函数内使用。await命令后面必须是Promise对象,然后返回该对象的结果。
-
await后面是Promise对象本身;await后面是原始类型的值(数值、字符串和布尔值,但这时会自动转成立即resolved的Promise对象)。然后直接返回对应的值。await后面是一个thenable对象(即定义了then方法的对象),await会将其视为Promise处理。
- 当函数执行的时候,一旦遇到
await就会先不往下执行,等await后的异步操作完成,再接着执行函数体内后面的语句。
function timeout(ms) {
console.log('timeout...', ms)
return new Promise((resolve) => {
console.log('timeout promise...', resolve)
setTimeout(()=>resolve('t resolve'), ms);
});
}
async function asyncPrint(value, ms) {
console.log(1)
// await命令后面的 Promise 对象,接收其resolve/reject 的参数
let p = await timeout(ms);
console.log(2, p, value, ms);
return value
}
// async 函数返回一个Promise对象, eg:asy
let asy = asyncPrint('hello world', 50)
console.log('asy...', asy)
// then 接收回调结果,参数是 async 内 return 的返回值, eg:v
// 只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
asy.then(v => console.log('then...',v));
console.log(3)
// 输出:
// 1
// timeout... 50
// timeout promise... ƒ () { [native code] }
// asy... Promise{}
// 3
// 2 't return' 'hello world' 50
// then... hello world
2.3. 错误处理
如果await后面的异步操作出错,那么等同于async函数返回的 Promise对象被reject。
为了防止出错,将await操作放在try...catch代码块之中。
async function main() {
try {
const val1 = await firstStep();
const val2 = await secondStep(val1);
const val3 = await thirdStep(val1, val2);
console.log('Final: ', val3);
}
catch (err) {
console.error(err);
}
}
3. 易错点
3.1. await命令后面必须放Promise对象 或 原始数据类型值、thenable对象。
3.2. await命令后的Promise对象声明时需要有有完整的从 pending 变为 resolved/reject 的部分,否则 Promise 会一直处于 pending 状态。
function timeoutPending() {
return new Promise(()=>{
setTimeout(()=>{
console.log('timeout-Pending')
},1)
})
}
function timeout() {
return new Promise((resolve)=>{
setTimeout(()=>{
console.log('timeout-Success')
resolve('success')
}, 10)
})
}
async function getFoo() {
// 返回 fulfilled 状态
let foo2 = await timeout()
console.log('2', foo2);
// 返回 pending 状态,不会往下执行
let foo1 = await timeoutPending()
console.log('1', foo1);
console.log(3);
}
// 输出:
// timeout-Success
// 2 success
// timeout-Pending
3.3. 多个请求串行与并行
3.3.1. 如下是串行与并行,两种写法的例子
- 写法1:
function getFoo() {
return new Promise((res)=>{
setTimeout(()=>res('getFoo'),2000)
})
}
function getBar() {
return new Promise((res)=>{
setTimeout(()=>res('getBar'),1000)
})
}
// 串行
async function getAll() {
console.log('1', new Date().getSeconds());
let foo = await getFoo();
console.log('2', new Date().getSeconds(), foo);
let bar = await getBar();
console.log('3', new Date().getSeconds(), bar);
}
getAll()
// 输出:
// 1 29
// Promise {<fulfilled>}
// 2 31 getFoo
// 3 32 getBar
function getFoo() {
return new Promise((res)=>{
setTimeout(()=>res('getFoo'),2000)
})
}
function getBar() {
return new Promise((res)=>{
setTimeout(()=>res('getBar'),1000)
})
}
// 并行
async function getAll() {
console.log('1', new Date().getSeconds());
let f = getFoo();
let b = getBar()
let foo = await f;
console.log('2', new Date().getSeconds(), foo);
let bar = await b;
console.log('3', new Date().getSeconds(), bar);
}
getAll()
// 输出:
// 1 32
// Promise {<fulfilled>}
// 2 34 getFoo
// 3 34 getBar
- 写法2:
// 串行
async function getAll() {
let getFoo = new Promise((res)=>{
setTimeout(()=>res('getFoo'),2000)
})
console.log('1', new Date().getSeconds());
let foo = await getFoo;
console.log('2', new Date().getSeconds(), foo);
let getBar = new Promise((res)=>{
setTimeout(()=>res('getBar'),1000)
})
let bar = await getBar;
console.log('3', new Date().getSeconds(), bar);
}
getAll()
// 输出:
// 1 45
// Promise {<fulfilled>}
// 2 47 getFoo
// 3 48 getBar
// 并行
async function getAll() {
console.log('1', new Date().getSeconds());
let getFoo = new Promise((res)=>{
setTimeout(()=>res('getFoo'),2000)
})
let getBar = new Promise((res)=>{
setTimeout(()=>res('getBar'),1000)
})
let foo = await getFoo;
console.log('2', new Date().getSeconds(), foo);
let bar = await getBar;
console.log('3', new Date().getSeconds(), bar);
}
getAll()
// 输出:
// 1 36
// Promise {<fulfilled>}
// 2 38 getFoo
// 3 38 getBar
3.3.2. 分析
- 现象
多个请求串行,是后一个等前一个执行成功后再去请求;多个请求并行,是所有请求一起去执行。
如上左侧是串行的例子,串行代码总执行时间需要3s,并行代码只需2s。
- 原因分析
如上例子串行与并行请求代码的区别,通过调整代码顺序即可实现。 可为什么呢?
其原因是 Promise的运行机制, Promise 新建(即: new Promise() )后就会立即执行。
如写法2:
- 串行代码,在创建
getFoo变量时,就开始执行了new Promise()内的代码了,遇到await后,等待pending状态改变为fulfilled/reject 后,继续执行下面的语句,过程同前面; - 而并行代码,一开始先创建
getFoo、getBar变量,就意味着将两个new Promise()内的代码的代码都执行了,再遇到await,等到pending状态改变为fulfilled/reject 后,继续执行下一个await。
-
- 如果第一个需要2s,第二个需要1s,则第一个await结果返回后,第二个结果也已经返回,则如例子所示,两个的时间是一样的;
- 如果如果第一个需要1s,第二个需要2s,则第一个await结果返回后,被第二个
await拦住等待状态改变,改变后再继续往下执行。
附:串、并行的 Promise 写法:
// 并行
async function getAll() {
console.log('1', new Date().getSeconds());
let getFoo = new Promise((res)=>{
setTimeout(()=>{res('getFoo');console.log('2', new Date().getSeconds(), getFoo);},2000)
})
let getBar = new Promise((res)=>{
setTimeout(()=>{res('getBar');console.log('3', new Date().getSeconds(), getBar)},1000)
})
}
// 串行
async function getAll() {
console.log('1', new Date().getSeconds());
let getFoo = new Promise((res)=>{
setTimeout(()=>{res('getFoo');console.log('2', new Date().getSeconds(), getFoo);},2000)
}).then((v)=>{
let getBar = new Promise((res)=>{
setTimeout(()=>{res('getBar');console.log('3', new Date().getSeconds(), getBar)},1000)
})
})
}
内容参考:
developer.mozilla.org/zh-CN/docs/… es6.ruanyifeng.com/#docs/async