JS异步编程是解决耗时操作阻塞主线程问题的核心方案,async函数是ES2017引入的语法糖,基于Promise实现,让异步代码的书写更接近同步逻辑,大幅降低了异步代码的阅读和维护成本。

async函数的基本特性
被async关键字修饰的函数会返回一个Promise对象,函数内部的返回值会被自动包装为Promise的resolve结果。如果async函数内部抛出错误,返回的Promise会变为reject状态。
// 基础async函数示例
async function getData() {
return 'hello async';
}
// 调用async函数得到的是Promise对象
getData().then(res => {
console.log(res); // 输出:hello async
});
// 抛出错误的async函数
async function throwError() {
throw new Error('异步错误');
}
throwError().catch(err => {
console.log(err.message); // 输出:异步错误
});
await关键字的使用规则
await只能在async函数内部使用,后面通常跟一个Promise对象,会暂停当前async函数的执行,等待Promise的状态变为resolve后,再继续执行后续代码,并把resolve的结果作为await表达式的返回值。
如果await后面不是Promise对象,会被自动转换为状态为resolve的Promise,直接返回该值。
// await等待Promise示例
function delay(ms) {
return new Promise(resolve => {
setTimeout(() => {
resolve(`等待了${ms}毫秒`);
}, ms);
});
}
async function testAwait() {
const result = await delay(1000);
console.log(result); // 1秒后输出:等待了1000毫秒
console.log('后续代码执行');
}
testAwait();
// await后面跟非Promise值
async function testNormalValue() {
const val = await 123;
console.log(val); // 输出:123
}
testNormalValue();
实战场景:异步串行与并行处理
异步串行
串行场景是指多个异步操作需要按顺序执行,前一个操作完成后再执行后一个,使用await依次等待即可实现。
// 模拟三个接口请求
function requestA() {
return new Promise(resolve => {
setTimeout(() => resolve('接口A返回数据'), 1000);
});
}
function requestB() {
return new Promise(resolve => {
setTimeout(() => resolve('接口B返回数据'), 800);
});
}
function requestC() {
return new Promise(resolve => {
setTimeout(() => resolve('接口C返回数据'), 500);
});
}
// 串行执行三个请求
async function serialRequest() {
const resA = await requestA();
console.log(resA); // 1秒后输出:接口A返回数据
const resB = await requestB();
console.log(resB); // 再过800毫秒输出:接口B返回数据
const resC = await requestC();
console.log(resC); // 再过500毫秒输出:接口C返回数据
return [resA, resB, resC];
}
serialRequest();
异步并行
并行场景是指多个异步操作之间没有依赖,可以同时发起,等待所有操作完成后再处理结果,此时需要配合Promise.all使用,避免逐个await导致的总耗时累加。
// 并行执行三个请求
async function parallelRequest() {
// 同时发起三个请求,不阻塞后续代码
const promiseArr = [requestA(), requestB(), requestC()];
// 等待所有请求完成
const results = await Promise.all(promiseArr);
console.log(results); // 约1秒后输出:["接口A返回数据", "接口B返回数据", "接口C返回数据"]
return results;
}
parallelRequest();
错误捕获的实战技巧
async函数内部的错误如果不捕获,会导致返回的Promise变为reject状态,有两种常用的捕获方式。
try-catch捕获
使用try-catch包裹可能出错的await逻辑,捕获到的错误可以在catch块中处理,避免错误向上抛出影响后续逻辑。
async function requestWithTryCatch() {
try {
const res = await requestA();
console.log(res);
// 模拟可能出错的请求
const errorRes = await Promise.reject(new Error('请求失败'));
console.log(errorRes);
} catch (err) {
console.log('捕获到错误:', err.message); // 输出:捕获到错误:请求失败
} finally {
console.log('请求流程结束');
}
}
requestWithTryCatch();
Promise.catch捕获
也可以直接对await后面的Promise调用catch方法,处理单个异步操作的错误,适合不需要中断后续逻辑的场景。
async function requestWithPromiseCatch() {
const res = await requestA().catch(err => {
console.log('请求A出错:', err.message);
return '默认数据';
});
console.log(res); // 如果请求A成功,输出接口返回数据,否则输出:默认数据
}
requestWithPromiseCatch();
注意事项与优化建议
- 不要在循环中使用await,会导致串行执行,增加总耗时,需要并行的话先收集所有Promise再用Promise.all处理。
- 顶层await目前仅在ES模块中支持,普通脚本中使用需要包裹在async函数中。
- 如果不需要等待异步结果就可以执行后续逻辑,不要滥用await,避免不必要的阻塞。
- 多个独立的异步操作尽量使用并行处理,减少整体执行时间。
async和await本质是Promise的语法糖,理解Promise的状态流转机制,能更好地掌握async编程的底层逻辑,遇到复杂异步场景时也能灵活处理。