async 和 await
案例
async 基础用法
js
// 声明异步函数
async function fn() {
return 'hello'; // 等价 return Promise.resolve('hello')
}
fn().then(res => console.log(res));await 等待 Promise
js
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function test() {
console.log('start');
await delay(1000);
console.log('after 1s');
}
test();并发串行
串行和并行的关键:Promise 创建时就执行,不是 await 时才执行
js
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function serial() {
console.log(1); // 立即
await delay(1000);
console.log(2); // 1s
await delay(1000);
console.log(3); // 2s
await delay(1000);
console.log(4); // 3s
}
serial();并发并行
串行和并行的关键:Promise 创建时就执行,不是 await 时才执行
js
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function parallel() {
const p1 = delay(1000);
const p2 = delay(1000);
const p3 = delay(1000);
console.log(1); // 立即
await p1;
console.log(2); // 1s
await p2;
console.log(3); // 1s
await p3;
console.log(4); // 1s
}
parallel();
// 标准并行 Promise.all
async function all() {
const [a, b, c] = await Promise.all([
delay(1000),
delay(1000),
delay(1000)
]);
}错误处理
js
// 方式一:async 内部 try/catch
async function fetchData() {
try {
const res = await fetch('/api');
const data = await res.json();
console.log(data);
} catch (err) {
console.error('请求失败', err);
}
}
// 方式二:async 内部 await 后面的 promise 对象.then
async function fn() {
await Promise.reject('出错');
}
fn().catch(err => console.log(err));
// 方式三:async 外部,调用自身函数的 .then,因为异步异常如果不捕获是会不断向上穿透传递的,最终还没捕获则会抛出异常
async function fn() {
const res = await Promise.reject('err').catch(err => err);
}与 Promise 静态方法结合
js
// await + Promise.all
const [data1, data2] = await Promise.all([api1(), api2()]);
// await + Promise.race(超时)
async function requestWithTimeout() {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject('超时'), 3000)
);
return await Promise.race([fetch(), timeout]);
}
// await + Promise.any
const res = await Promise.any([api1(), api2(), api3()]);
// await + Promise.allSettled
const results = await Promise.allSettled([p1, p2, p3]);循环中的 await
js
// for 循环:可串行
async function loop() {
const arr = [1000, 2000, 3000];
for (const ms of arr) {
await delay(ms);
}
// 串行,总耗时 6s
}
// forEach 里不能用 await(无效)
// 错误写法
async function bad() {
[1,2,3].forEach(async item => {
await delay(1000); // 不会等待
});
console.log('done'); // 立即执行
}接口请求 + 加载状态
js
async function loadList() {
setLoading(true);
try {
const res = await api.getList();
setList(res);
} catch (err) {
showToast('加载失败');
} finally {
setLoading(false);
}
}表单提交(串行校验 + 提交)
js
async function submit() {
await validateForm(); // 校验
await beforeSubmit(); // 前置处理
await submitForm(); // 提交
alert('提交成功');
}上传多个文件(并行)
js
async function uploadFiles(files) {
const tasks = files.map(file => upload(file));
const result = await Promise.all(tasks);
return result;
}接口重试
js
async function retry(fn, times = 3) {
try {
return await fn();
} catch (err) {
if (times > 0) return retry(fn, times - 1);
throw err;
}
}限制并发请求(100 个接口只同时发 3 个)
js
async function limitRequest(tasks, limit = 3) {
const result = [];
const pool = [];
for (const task of tasks) {
const p = task().then(res => result.push(res));
pool.push(p);
if (pool.length >= limit) {
await Promise.race(pool);
}
}
await Promise.all(pool);
return result;
}进阶练习
shell
# 1. 什么是 async/await?
async/await 是 ES2017 推出的异步语法,基于 Promise+Generator 实现的语法糖,目的是让异步代码写起来像同步代码一样清晰,解决回调地狱和 then 链过长的问题。
# 2. 说说async 和 await关键字的作用
async:用于声明一个函数是异步函数,执行后一定返回 Promise,返回普通值自动包装成Promise.resolve(值),返回 Promise 则直接用,抛出错误 包装成 Promise.reject(err)
await:只能在 async 函数内部使用,等待一个 Promise 完成,如果 await 后面是普通值则直接返回结果
# 3. 错误处理
方式一:在 async 函数内部,使用 try catch 包裹整个 await 语句
方式二:在 async 函数内部,使用 await 后面 promise的对象的 .then 方法
方式二:在 async 函数外部,调用 async 异步函数使用 .catch 方法
## 补充:为什么要尽量用 try/catch?
可以精准捕获某一步异步错误,不影响后续逻辑,比全局 catch 更可控、更优雅。
# 4. await 后面跟非 Promise 会怎样?
自动包装成 Promise.resolve(值) 并立即执行。
# 5. async 函数默认返回什么?
Promise { undefined }
# 6. 如何中断 async 函数?
return 一个值
throw new Error()
返回 Promise.reject ()
# 7. await 会阻塞 JS 主线程吗?
不会。它只阻塞当前这个 async 函数内部的执行,不会阻塞整个 JS 执行栈,外面代码照样运行。
# 8. 多个 await 是串行还是并行?
默认是串行,一个执行完再执行下一个,总耗时是所有时间相加。
## 怎么让多个异步并行执行?
await Promise.all([])
# 9. forEach 里能用 await 吗?为什么?
不推荐,也达不到预期效果。
forEach 内部是回调函数,不会等待 await,会瞬间同步执行完,变成并发失控。
要串行循环用 for...of + await,要并行用 map + Promise.all。
# 10. async/await 和 Promise 有什么关系?
async/await 底层就是 Promise + Generator 实现的语法糖
所有 async/await 都能改写成 Promise then 写法,反之亦然
它没有改变 Promise 机制,只是写法更简洁
# 11.async/await 相比 then 链有什么优势?
代码扁平,没有嵌套,可读性更强
调试方便,错误栈更清晰
条件判断、循环、错误处理更符合同步思维
# 12. 有了 async/await 还需要 Promise 吗?
需要。async/await 只是消费 Promise,创建异步、并发控制(all/race/any)还是要靠 Promise 静态方法。
# 13. 什么是异常穿透?
在 then 链或 async 函数中,错误会一直向后传递,直到被 catch 捕获,这就是异常穿透。
# 执行顺序:同步先跑 → 微任务随后 → 宏任务最后,await 下面的代码相当于 then 里的微任务。
async function async1() {
console.log(1);
await async2();
console.log(2);
}
async function async2() {
console.log(3);
}
console.log(4);
async1();
console.log(5);
4
1
3
5
2注意
shell
串行和并行的关键:Promise 创建时就执行,不是 await 时才执行,这点很关键、非常关键