当前位置:网站首页>async/await用法详解
async/await用法详解
2022-07-30 05:40:00 【Tab_2021】
ES2017 标准引入了 async 函数,使得异步操作变得更加方便。
async函数的定义方式
// 函数声明
async function fn() {
}
// 函数表达式
const fn = async function () {
}
// 箭头函数
const fn = async () => {
}
// 对象的方法
let obj = {
async fn() {
} }
obj.fn().then(...)
核心用法
async函数会返回一个Promise对象。若返回的不是一个显示的Promise对象,则会使用Promise.resolve()包装成Promise对象。
await命令后跟一条返回Promise对象的语句。若语句返回的不是Promise对象,则会使用Promise.resolve()包装成Promise对象。
await命令相当于其后Promise对象调用的只有一个回调函数的then方法。async函数体内await命令所在语句后的语句相当于then方法第一个回调函数中的语句,await命令返回的值相当于传入then方法第一个回调函数的参数。
示例代码:
async function fn() {
return 100 // 相当于 return Promise.resolve(100)
}
console.log(fn()) // Promise {<fulfilled>: 100}
(async function () {
const a = await new Promise((resolve, reject) => {
console.log('start')
resolve('hello')
console.log('middle')
}).then((val) => {
console.log(val)
return 'world'
})
console.log(a)
})()
console.log('end')
// start
// middle
// end
// hello
// world
错误处理
因为async函数体内await命令所在语句后的语句相当于then方法第一个回调函数中的语句,所以如果任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。
(async function () {
await Promise.reject('出错了')
await Promise.resolve('hello world'); // 不会执行
})()
console.log('hello') // 仍然可以执行
常见错误方式
1. await后的Promise变为reject状态
await命令后面的 Promise 对象如果变为rejected状态,则reject的参数会被catch方法的回调函数接收到。
(async function () {
await Promise.reject('出错了')
console.log('hello'); // 不会执行
})()
.catch(e => console.log('err~', e))
// err~ 出错了
注意,上面代码中,await语句前面没有return,但是reject方法的参数依然传入了catch方法的回调函数。这里如果在await前面加上return,效果是一样的。
2. await后的异步操作出错
若Promise内部直接抛出错误(如以下的throw new Error('出错了')),则Promise状态会变为rejected,reject的参数即为抛出的错误对象(如以下的Error('出错了'))
(async function () {
await new Promise(() => {
throw new Error('出错了')
console.log('hello') // 不会执行
})
})()
.catch(e => console.log('err~', e))
// err~ Error: 出错了
3. async函数内部抛出错误
async函数内部直接抛出错误,会导致async函数返回的 Promise 对象变为rejected状态。抛出的错误对象会被catch方法回调函数接收到。
(async function () {
throw new Error('出错了');
console.log('hello'); // 不会执行
})()
.catch(e => console.log('err~', e))
// err~ Error: 出错了
// 类似于如下
new Promise(() => {
throw new Error('出错了')
console.log('hello'); // 不会执行
})
.catch(e => console.log('err~', e))
错误处理方式
如果希望即使一个异步操作失败,也不要中断后面的异步操作,可以将可能出错的await放在try...catch结构里面,这样不管这个异步操作是否成功,后面的await都会执行。
async function f() {
try {
await Promise.reject('出错了')
} catch (e) {
}
return await Promise.resolve('hello world')
}
f().then(v => console.log(v))
// hello world
如果await后跟的是Promise对象,则也可以后面的 Promise 对象再跟一个catch方法,处理前面可能出现的错误。
async function f() {
await Promise.reject('出错了')
.catch(e => console.log(e));
return await Promise.resolve('hello world');
}
f().then(v => console.log(v))
// 出错了
// hello world
如果有多个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);
}
}
Promise对象的状态变化
async函数返回的 Promise 对象,必须等到async函数内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
示例代码:
function getSquare(num) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(num ** 2)
}, 1000)
})
}
async function fn(num) {
const val1 = await getSquare(num)
const val2 = await getSquare(val1)
return val2
}
fn(3).then(console.log)
// 延迟两秒后,打印81
上面代码中,函数fn内部有两个操作:将输入平方、将得到的结果再次平方。只有这两个操作全部完成,才会执行then方法里面的console.log。
继发与并发
多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
let foo = await getFoo();
let bar = await getBar();
上面代码中,getFoo和getBar是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo完成以后,才会执行getBar,完全可以让它们同时触发。
如下写法,getFoo和getBar是同时触发,这样就会缩短程序的执行时间。
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
示例代码:
// 定义求平方函数
function getSquare(num) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(num ** 2)
}, 1000)
})
}
// 继发处理
!(async function () {
const arr = [1, 2, 3]
const res = []
for (const num of arr) {
res.push(await getSquare(num))
}
console.log(res)
})()
// 延时3秒后,打印[1, 4, 9]
// 并发处理
!(async function () {
const arr = [1, 2, 3]
const promiseArr = arr.map(x => getSquare(x))
let res = await Promise.all(promiseArr)
console.log(res)
})()
// 延时1秒后,打印[1, 4, 9]
遍历
数组的forEach()、map()等方法都不支持按顺序等待异步处理(即不支持上述继发处理),for、for...in、for...of 都会按顺序等待异步。
示例代码:
function getSquare(num) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(num ** 2)
}, 1000)
})
}
!(function () {
const arr = [1, 2, 3]
arr.forEach(async num => {
console.log(await getSquare(num));
})
})()
// 延时1秒后,同时打印1 4 9
!(async function () {
const arr = [1, 2, 3]
for (const num of arr) {
console.log(await getSquare(num));
}
})()
// 延时1秒打印1,再延时1秒打印4,再延时1秒打印9
注意事项
await命令只能用在async函数之中,如果用在普通函数中会报错。async函数体内,await命令之前的代码以及await命令所在语句中await后的代码都为正常的同步代码,在async函数调用时正常执行。await命令后的Promise对象的运行结果可能是rejected,所以最好把await命令放在try...catch代码块中。
边栏推荐
猜你喜欢

在弹性布局flex布局中,行内标签也能直接加宽高

Socket通信编程

Qt实现单击或双击QTableWidge/View表头进行排序

Mysql

三子棋游戏实现(c语言)
![[Other] DS5](/img/20/6863bb7b58d2e60b35469ba32e5830.png)
[Other] DS5

“tensorflow.keras.preprocessing“ has no attribute “image_dataset_from_directory“

union中有struct的情况-从内存分析

cmd (command line) to operate or connect to the mysql database, and to create databases and tables

Navicat new database
随机推荐
Graphic mirror symmetry (schematic diagram)
50道SQL练习题(刷完直接进大厂)
MySQL Soul 16 Questions, how many questions can you last?
多线程并发服务器
240.搜索二维矩阵II
Qt实现单击或双击QTableWidge/View表头进行排序
String类型字符串获取第一次或者最后一次出现的下标
torch.load()
Error: npm ERR code EPERM
[详解C语言]一文带你玩转数组
操作系统面试整理
每日练习------输出一个整数的二进制数、八进制数、十六进制数。
Ranking of grades (Huazhong University of Science and Technology postgraduate examination questions) (DAY 87)
div设置一个最小高度和最大高度,但是中间可以靠内容撑开
cmd (command line) to operate or connect to the mysql database, and to create databases and tables
P3 元宝序列化笔记
相对路径和绝对路径的区别
C语言:通过函数实现一个整形有序数组的二分查找
多进程实现并发服务器
Basic syntax of MySQL DDL and DML and DQL