JavaScript 是单线程语言,但前端开发里经常要处理异步任务,比如请求接口、读取文件、定时器、用户事件、动画回调等。异步编程写得清楚,页面交互会更稳定;写得混乱,就容易出现回调嵌套、错误丢失、状态错乱等问题。
现代 JavaScript 主要通过 Promise 和 async/await 处理异步逻辑。本文从异步任务的基本概念讲起,再介绍 Promise 链式调用、async/await 写法和错误处理实践。
为什么需要异步
浏览器执行 JavaScript 时,如果某个任务耗时很长,页面就可能卡住。网络请求、定时等待、文件读取这些操作不能让主线程一直阻塞,所以它们通常以异步方式执行。
异步的意思不是任务更快完成,而是先把耗时任务交出去,等结果回来后再继续处理。这样页面可以继续响应用户操作。
回调函数问题
早期异步代码常用回调函数。简单场景没问题,但多个异步任务依赖彼此时,很容易形成多层嵌套。
getUser((user) => {
getOrders(user.id, (orders) => {
getDetail(orders[0].id, (detail) => {
console.log(detail);
});
});
});
这种写法被称为回调地狱。代码越深,错误处理和流程控制越难维护。Promise 的出现就是为了解决这类问题。

Promise 基础
Promise 表示一个未来会完成或失败的异步结果。它有三种状态:等待中、已成功、已失败。状态一旦改变,就不会再回退。
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功结果');
}, 1000);
});
成功时调用 resolve,失败时调用 reject。实际项目里,我们更多是使用接口请求或工具函数返回的 Promise,而不是每次手动 new。
then 和 catch
Promise 可以通过 then 获取成功结果,通过 catch 捕获错误。
fetch('/api/user')
.then((response) => response.json())
.then((data) => {
console.log(data);
})
.catch((error) => {
console.error('请求失败', error);
});
链式调用可以避免多层回调嵌套。每个 then 返回的结果,会传给下一个 then。
Promise.all
如果多个异步任务可以并行执行,并且都完成后再继续,可以使用 Promise.all。
const [user, orders] = await Promise.all([
fetchUser(),
fetchOrders()
]);
需要注意,只要其中一个 Promise 失败,Promise.all 就会整体失败。如果希望每个任务独立返回结果,可以考虑 Promise.allSettled。
async 和 await
async/await 是 Promise 的语法糖,它让异步代码看起来更像同步流程。函数前加 async 后,内部可以使用 await 等待 Promise 结果。
async function loadUser() {
const response = await fetch('/api/user');
const data = await response.json();
console.log(data);
}
await 只会暂停当前 async 函数的后续执行,不会阻塞整个浏览器主线程。页面仍然可以继续响应其他事件。
错误处理
在 async 函数里,常用 try...catch 捕获异步错误。
async function loadUser() {
try {
const response = await fetch('/api/user');
const data = await response.json();
return data;
} catch (error) {
console.error('加载用户失败', error);
throw error;
}
}
捕获错误后,要根据业务决定是否继续抛出。如果上层还需要统一处理,就应该重新抛出;如果当前函数已经处理完错误,可以返回兜底结果。
接口状态检查
fetch 在 HTTP 404 或 500 时不会自动进入 catch,只有网络错误才会 reject。因此要手动检查 response.ok。
async function request(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.json();
}
这是很多新手容易忽略的点。接口返回错误状态码时,也应该走统一错误处理流程。
避免串行等待
如果两个异步任务互不依赖,不要写成串行 await。串行会让总耗时变长。
const userPromise = fetchUser();
const configPromise = fetchConfig();
const user = await userPromise;
const config = await configPromise;
更简洁的写法是使用 Promise.all。只有后一个任务依赖前一个结果时,才需要串行等待。
finally 清理状态
无论成功还是失败,都需要执行的清理逻辑,可以放在 finally 里。比如关闭 loading、恢复按钮状态。
async function submit() {
loading = true;
try {
await saveForm();
} finally {
loading = false;
}
}
这样可以避免请求失败后 loading 一直不消失。表单提交、弹窗确认、上传文件等场景都很常用。
常见错误
第一种错误是忘记写 await,拿到的不是结果而是 Promise。第二种错误是在循环里无脑 await,导致本可并行的任务变成串行。第三种错误是 catch 后吞掉错误,上层完全不知道失败。第四种错误是没有处理 loading 和重复提交状态。
还有一种常见问题是把 async 函数传给事件处理器后,内部错误没有被妥善捕获。涉及用户操作的异步逻辑,最好有明确的错误提示。
实践建议
写异步代码时,先判断任务之间是否有依赖。互不依赖就并行,存在依赖才串行。请求函数要统一处理 HTTP 状态和 JSON 解析。页面交互要用 try、catch、finally 管理成功、失败和清理状态。
Promise 和 async/await 不是两套完全不同的东西,async/await 只是让 Promise 写起来更顺。理解 Promise 的状态和错误传播,再使用 async/await,异步代码就会清晰很多。















暂无评论内容