JavaScript async/await 错误处理:别让 Promise 静默失败

async/await 让异步代码看起来像同步代码,但也让错误更容易被忽略:一个没接住的 reject、一个没 await 的 Promise、一次被空 catch 吞掉的异常,都可能让程序静默失败------页面没反应、数据没写入、日志里却什么都没有。本文讲 6 个最常见的坑和对应的稳妥写法。

1. 先立规矩:await 要放在 try 里面

async 函数里抛出的错误,本质上是返回的 Promise 被 reject。如果 await 外面没有 try/catch,错误会沿着调用链向上传递,直到没人处理。

```js

async function loadUser(id) {

try {

const res = await fetch(`/api/users/${id}`);

if (!res.ok) {

throw new Error(`请求失败:${res.status}`);

}

return await res.json();

} catch (err) {

console.error('加载用户失败', err);

throw err; // 别在这里悄悄吞掉,让调用方决定怎么处理

}

}

```

注意两点:第一,fetch 本身只在网络失败时 reject,HTTP 404、500 不会自动抛错,所以要手动检查 res.ok;第二,catch 里如果只是打印日志又不重抛,调用方会误以为函数成功了。

2. 最容易漏的:没有 await 的 Promise

下面这段代码看起来没问题,实际上 try/catch 根本抓不到错误:

```js

// 错误写法:save() 返回的 Promise 没人 await,也没人 catch

async function handleSubmit(data) {

try {

save(data); // 漏了 await

showToast('已保存');

} catch (err) {

showToast('保存失败');

}

}

```

save() 内部 reject 时,这个 Promise 变成无人处理的 rejected Promise,页面却已经提示"已保存"。修复方法很简单:要么 await 它,要么显式接住它。

```js

async function handleSubmit(data) {

try {

await save(data);

showToast('已保存');

} catch (err) {

showToast('保存失败');

}

}

```

一条可以贴在工位上的规则:创建 Promise 就要立刻决定它的归属------await、return,或者 .catch(),不要让它"裸奔"。

3. 并行任务:Promise.all 和 Promise.allSettled 别用反

Promise.all 有一个失败,整个结果就失败,其他任务的结果也拿不到;适合"缺一个就不行"的场景,比如同时拉取用户信息和权限,缺了谁都不能渲染。

```js

try {

const user, permission = await Promise.all([

fetchUser(id),

fetchPermission(id),

]);

render(user, permission);

} catch (err) {

renderError('关键数据加载失败');

}

```

如果任务之间彼此独立,比如批量给 10 个用户发通知,某个失败不应该拖垮全部,就用 Promise.allSettled,事后再逐个检查 status:

```js

const results = await Promise.allSettled(users.map((u) => sendNotice(u)));

const failed = results.filter((r) => r.status === 'rejected');

if (failed.length > 0) {

console.error('部分通知发送失败', failed);

}

```

选错组合的代价很具体:该用 allSettled 的地方用了 all,一次偶发失败会让整批任务白跑;该用 all 的地方用了 allSettled,后续代码又可能在缺数据的情况下继续跑。

4. 循环里的错误:一次失败不要拖垮整批

用 for...of 顺序处理时,把 try/catch 放在循环体内,单个失败只跳过当次,不中断整批:

```js

for (const file of files) {

try {

await upload(file);

} catch (err) {

console.error(`上传失败:${file.name}`, err);

// 记录失败项,继续处理下一个

}

}

```

如果把 try/catch 包在循环外面,第一个失败就会直接跳出循环,后面的文件一个都不会处理。反过来,如果追求速度而用 map + Promise.all,也要先想清楚:这批任务是不是允许一个失败就全部作废。

5. 不要写空 catch,也不要只打印日志就当处理完了

这两种写法在代码评审里都应该被拦下来:

```js

try {

await syncData();

} catch (err) {} // 坏:错误被彻底吞掉,排查时无从下手

try {

await syncData();

} catch (err) {

console.log(err); // 不够:用户看到的是"没反应",而不是"同步失败,请重试"

}

```

一个可用的错误处理至少要做到三选一:重抛给调用方、返回一个调用方能区分的失败结果、或者给用户一个可操作的反馈并上报日志。只打印日志,等于把问题留给下一个排查的人。

6. 给异步操作加超时:别让请求无限挂起

网络请求可能既不成功也不失败,只是长时间挂起。结合 AbortController 和 Promise.race 可以给它一个明确的上限:

```js

async function fetchWithTimeout(url, ms = 5000) {

const controller = new AbortController();

const timer = setTimeout(() => controller.abort(), ms);

try {

const res = await fetch(url, { signal: controller.signal });

if (!res.ok) throw new Error(`请求失败:${res.status}`);

return await res.json();

} finally {

clearTimeout(timer); // 无论成功失败,都要清理定时器

}

}

```

这里 finally 的作用很关键:请求正常返回后如果不清理定时器,定时器仍会在后台触发一次 abort,虽然对已完成请求无害,但属于不必要的副作用。

发布前自查清单

  • 每个 await 是否都在 try/catch 的保护范围内?

  • 有没有创建后既没 await、又没 return、也没 .catch() 的 Promise?

  • 并行任务用的是 all 还是 allSettled,是否符合"缺一不可还是彼此独立"?

  • catch 里是重抛、返回失败结果,还是给了用户反馈?有没有空 catch?

  • 可能挂起的请求有没有超时和清理逻辑?

async/await 的错误处理没有魔法,核心就一句话:让每一个 Promise 都有明确的归属,让每一次失败都有明确的去向。做到这两点,静默失败就会少很多。

相关推荐
运维行者_1 小时前
供应链安全成最大风险源,企业如何构建可控的运维体系?
运维·开发语言·网络·数据库·安全·云计算·流量监控
.YM.Z1 小时前
优选算法——【双指针算法2】C++实现
开发语言·c++·算法
逆向编程1 小时前
MATLAB高级数据结构:cell、struct与table深度应用
开发语言·数据结构·matlab
PedroQue991 小时前
v0.2.0 架构升级:class + reactive 重塑状态管理
前端·uni-app
鶴哥只手遮天1 小时前
Angular 22 源码级深度解析-第 02 章 编译链路总览:从模板字符串到 `ɵɵ` 指令
前端·开源·angular.js
尘世中一位迷途小书童1 小时前
别再堆万字 Prompt:用 Agent Skills 给团队写一个「前端 Review」Skill
前端·ai编程·cursor
aixingpan1 小时前
aixingpan.cn API开发文档:api_docs_bichart_common2接口指南
前端·php
super_Omni1 小时前
text/PDF/Word → 解析成纯文本 → 分词 → 词表匹配 → 加权打分 → 低于阈值
前端·chrome
阶进遐1 小时前
浏览器工具自制指南·对话导出(3):消息池设计
前端