Promise 与 async/await:使用方式、错误传播与捕获

Promise 与 async/await:使用方式、错误传播与捕获

JavaScript 中很多操作不会立即完成,例如网络请求、定时器和文件读取。Promise 用来表示一个“现在还没有结果,但未来会产生结果”的异步操作;async/await 则是在 Promise 之上提供的一种更接近同步代码的写法。

async/await 并不是另一套异步机制,而是建立在 Promise 之上的语法糖。

一、Promise 的定义与状态

一个 Promise 只能处于三种状态之一:

  • pending:等待中
  • fulfilled:成功完成
  • rejected:执行失败

状态只能改变一次:

1
2
pending → fulfilled
pending → rejected

一旦进入 fulfilled 或 rejected,状态和最终结果便不会再改变。

创建 Promise

1
2
3
4
5
6
7
8
9
const promise = new Promise((resolve, reject) => {
const success = true;

if (success) {
resolve("操作成功");
} else {
reject(new Error("操作失败"));
}
});
  • resolve(value):把 Promise 变成 fulfilled,并提供成功值。
  • reject(error):把 Promise 变成 rejected,并提供失败原因。

推荐使用 Error 对象,而不是拒绝一个普通字符串:

1
2
reject(new Error("用户不存在")); // 推荐
reject("用户不存在"); // 不推荐

Error 对象包含错误类型、错误信息和调用栈,更便于定位问题。

二、then、catch 和 finally

1. 使用 then 处理成功结果

1
2
3
Promise.resolve(10).then(value => {
console.log(value); // 10
});

.then() 会返回一个新的 Promise,所以可以链式调用:

1
2
3
4
5
6
Promise.resolve(10)
.then(value => value * 2)
.then(value => value + 5)
.then(value => {
console.log(value); // 25
});

前一个 .then() 的返回值会成为下一个 .then() 接收到的值:

1
10 → 20 → 25

2. then 返回 Promise 时会自动等待

1
2
3
4
5
6
7
8
9
10
11
function delayDouble(value) {
return new Promise(resolve => {
setTimeout(() => resolve(value * 2), 1000);
});
}

Promise.resolve(10)
.then(value => delayDouble(value))
.then(result => {
console.log(result); // 一秒后输出 20
});

下一个 .then() 不会收到原始 Promise 对象,而会等待它完成并收到成功值。这种自动展开 Promise 的行为,是 Promise 链能够表达连续异步操作的关键。

三、Promise 错误如何产生和传播

Promise 链中,以下情况都会使当前步骤返回的 Promise 变成 rejected:

  1. 返回一个 rejected Promise。
  2. 在回调中直接抛出异常。
1
2
3
4
5
Promise.resolve()
.then(() => Promise.reject(new Error("数据库连接失败")))
.catch(error => {
console.error(error.message);
});

直接抛出异常也会得到相同的传播效果:

1
2
3
4
5
6
7
Promise.resolve()
.then(() => {
throw new Error("数据格式错误");
})
.catch(error => {
console.error(error.message);
});

在 Promise 回调中,下面两种写法的传播效果近似:

1
2
throw error;
return Promise.reject(error);

错误会沿 Promise 链向后传播

如果某一步失败,之后没有错误处理器的成功回调会被跳过,直到找到最近的 .catch():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Promise.resolve("开始")
.then(value => {
console.log(value);
throw new Error("第二步失败");
})
.then(() => {
console.log("不会执行");
})
.then(() => {
console.log("也不会执行");
})
.catch(error => {
console.error("捕获到:", error.message);
});

输出:

1
2
开始
捕获到:第二步失败

.catch(handler) 基本等价于 .then(undefined, handler)。

catch 可以恢复,也可以继续传播错误

如果 .catch() 返回普通值,它生成的新 Promise 会变为 fulfilled,后续 .then() 可以继续执行:

1
2
3
4
5
6
7
8
Promise.reject(new Error("请求失败"))
.catch(error => {
console.error(error.message);
return "默认数据";
})
.then(value => {
console.log(value); // 默认数据
});

如果希望上层仍然知道发生了错误,应重新抛出:

1
2
3
4
5
6
7
8
Promise.reject(new Error("请求失败"))
.catch(error => {
console.error("记录日志:", error.message);
throw error;
})
.catch(error => {
console.error("上层捕获:", error.message);
});

finally 的行为

.finally() 无论 Promise 成功还是失败都会运行,适合关闭加载状态或释放资源:

1
2
3
4
5
6
requestData()
.then(data => console.log(data))
.catch(error => console.error(error))
.finally(() => {
console.log("请求结束");
});

它通常不改变原来的结果:

1
2
3
Promise.resolve(10)
.finally(() => console.log("清理资源"))
.then(value => console.log(value)); // 10

但如果 .finally() 自己抛出错误,新错误会覆盖原结果:

1
2
3
4
5
6
7
Promise.resolve("原始结果")
.finally(() => {
throw new Error("清理失败");
})
.catch(error => {
console.log(error.message); // 清理失败
});

四、Promise 构造函数的捕获范围

Promise 执行器中的同步异常会自动转化为 rejection:

1
2
3
4
5
6
7
const promise = new Promise(() => {
throw new Error("同步异常");
});

promise.catch(error => {
console.log(error.message); // 同步异常
});

但是,稍后执行的异步回调所抛出的异常不会自动被外层 Promise 捕获:

1
2
3
4
5
6
7
8
9
const promise = new Promise(() => {
setTimeout(() => {
throw new Error("异步异常");
}, 1000);
});

promise.catch(() => {
// 捕获不到上面的异常
});

因为定时器回调运行时,Promise 执行器早已结束。需要在异步回调中主动调用 reject:

1
2
3
4
5
6
7
8
9
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
try {
throw new Error("异步异常");
} catch (error) {
reject(error);
}
}, 1000);
});

如果异步 API 本身已经返回 Promise,不要再无意义地包装一层:

1
2
3
4
5
6
7
8
9
10
11
// 不必要的包装
function getData() {
return new Promise((resolve, reject) => {
fetch("/api/data").then(resolve).catch(reject);
});
}

// 直接返回即可
function getData() {
return fetch("/api/data");
}

五、async 函数

在函数前添加 async,该函数就一定会返回 Promise:

1
2
3
4
5
6
7
async function getNumber() {
return 10;
}

const result = getNumber();
console.log(result instanceof Promise); // true
result.then(console.log); // 10

它大体等价于:

1
2
3
function getNumber() {
return Promise.resolve(10);
}

如果 async 函数抛出错误,它返回的 Promise 会变成 rejected:

1
2
3
4
5
6
7
async function getUser() {
throw new Error("用户不存在");
}

getUser().catch(error => {
console.log(error.message); // 用户不存在
});

因此下面的同步 try/catch 捕获不到该拒绝:

1
2
3
4
5
try {
getUser();
} catch (error) {
// 捕获不到
}

正确方式是调用 .catch(),或者在另一个 async 函数中用 await 和 try/catch。

六、await 的含义

await 会等待一个 Promise 完成:

1
2
3
4
async function main() {
const value = await Promise.resolve(10);
console.log(value); // 10
}
  • Promise fulfilled:await 表达式得到成功值。
  • Promise rejected:await 会在当前位置抛出拒绝原因。
1
2
3
async function main() {
const result = await Promise.reject(new Error("请求失败"));
}

这里的失败会使 main() 返回一个 rejected Promise。若要在当前函数处理错误,应使用 try/catch:

1
2
3
4
5
6
7
8
9
10
async function loadUser() {
try {
const response = await fetch("/api/user");
const user = await response.json();
return user;
} catch (error) {
console.error("加载用户失败:", error);
return null;
}
}

如果其中任何一个被等待的 Promise 失败,之后的语句都会被跳过,控制流直接进入 catch:

1
2
3
4
5
6
7
8
9
10
async function run() {
try {
const a = await stepA();
const b = await stepB(a);
const c = await stepC(b);
return c;
} catch (error) {
console.error("某一步失败:", error);
}
}

它与下面的 Promise 链具有相同的错误传播结构:

1
2
3
4
stepA()
.then(stepB)
.then(stepC)
.catch(error => console.error(error));

七、async/await 中的错误向上传播

如果没有 try/catch,或者捕获后重新抛出,错误会使当前 async 函数返回 rejected Promise:

1
2
3
4
5
6
7
8
9
10
11
12
13
async function getProfile() {
const user = await getUser();
return getUserProfile(user.id);
}

async function main() {
try {
const profile = await getProfile();
console.log(profile);
} catch (error) {
console.error("最终捕获:", error);
}
}

传播过程如下:

1
2
3
4
5
6
7
8
9
底层 Promise rejected
↓
await 在当前位置抛出错误
↓
getProfile 返回 rejected Promise
↓
main 中的 await 再次抛出
↓
main 的 catch 捕获

捕获并恢复

1
2
3
4
5
6
7
8
async function getConfig() {
try {
return await requestConfig();
} catch (error) {
console.error("配置请求失败:", error);
return { theme: "light" };
}
}

因为 catch 返回了普通值,getConfig() 最终是 fulfilled。

捕获后继续传播

1
2
3
4
5
6
7
8
async function getConfig() {
try {
return await requestConfig();
} catch (error) {
console.error("记录错误:", error);
throw error;
}
}

包装错误并保留原始原因

1
2
3
4
5
6
7
8
9
async function getUser(id) {
try {
return await database.findUser(id);
} catch (error) {
throw new Error(`查询用户 ${id} 失败`, {
cause: error
});
}
}

这样既能增加业务上下文,也能通过 error.cause 保留底层错误。

八、return promise 与 return await

通常下面两种写法最终结果相近:

1
2
3
4
5
6
7
async function getData() {
return requestData();
}

async function getData() {
return await requestData();
}

但在当前函数的 try/catch 中,两者有重要区别:

1
2
3
4
5
6
7
8
async function getData() {
try {
return requestData();
} catch (error) {
// 通常捕获不到 requestData 返回的 Promise 后续发生的拒绝
return null;
}
}

使用 return await,拒绝会在当前 try 中被转换成抛出,因此可以被当前 catch 捕获:

1
2
3
4
5
6
7
8
async function getData() {
try {
return await requestData();
} catch (error) {
console.error("请求失败:", error);
return null;
}
}

经验规则:

  • 只是把 Promise 交给调用者处理:可以 return promise。
  • 需要在当前 try/catch 中处理失败:使用 return await promise。

九、HTTP 失败不一定等于 Promise 失败

fetch() 遇到网络错误或请求被中断时会 reject,但 HTTP 404、500 通常仍然会返回 fulfilled Promise。因此要主动检查状态码:

1
2
3
4
5
6
7
8
9
10
11
async function requestUser() {
const response = await fetch("/api/user");

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

return response.json();
}

这说明“技术上的 Promise 成功”和“业务操作成功”并不总是同一件事,业务失败往往需要程序主动判断并抛出。

十、并发 Promise 的错误传播

Promise.all

等待全部 Promise 成功;任何一个失败,整体就会 reject:

1
2
3
4
5
6
7
8
9
10
11
12
13
async function loadPage() {
try {
const [user, articles, messages] = await Promise.all([
getUser(),
getArticles(),
getMessages()
]);

return { user, articles, messages };
} catch (error) {
console.error("至少一个请求失败:", error);
}
}

注意:Promise.all() 失败并不意味着其他操作会自动取消,它们通常仍会继续运行。

Promise.allSettled

等待全部 Promise 完成,并分别报告成功或失败:

1
2
3
4
5
6
const results = await Promise.allSettled([
Promise.resolve("A"),
Promise.reject(new Error("B 失败"))
]);

console.log(results);

结果类似:

1
2
3
4
[
{ status: "fulfilled", value: "A" },
{ status: "rejected", reason: Error("B 失败") }
]

适合所有任务都必须完成,并需要分别统计结果的场景。

Promise.any

只要有一个成功,整体就成功;全部失败时抛出 AggregateError:

1
2
3
4
5
6
7
8
9
10
11
try {
const result = await Promise.any([
requestServerA(),
requestServerB(),
requestServerC()
]);
console.log(result);
} catch (error) {
console.error(error instanceof AggregateError);
console.error(error.errors);
}

Promise.race

哪个 Promise 最先结束,就采用哪个结果,无论成功还是失败:

1
2
3
4
5
6
7
8
9
10
11
12
13
const timeout = new Promise((_, reject) => {
setTimeout(() => reject(new Error("请求超时")), 3000);
});

try {
const result = await Promise.race([
requestData(),
timeout
]);
console.log(result);
} catch (error) {
console.error(error.message);
}

race() 只决定采用谁的结果,并不会自动取消其他任务。

十一、串行和并行 await

下面是串行执行,第二个任务要等待第一个完成后才开始:

1
2
const user = await getUser();
const articles = await getArticles();

如果两者互不依赖,应使用并行方式:

1
2
3
4
const [user, articles] = await Promise.all([
getUser(),
getArticles()
]);

并行可以减少总等待时间,并让错误处理集中在同一个 try/catch 中。

十二、常见错误写法

1. 忘记 await

1
2
3
4
5
6
7
8
async function main() {
try {
const user = getUser();
console.log(user.name); // user 是 Promise
} catch (error) {
console.error(error);
}
}

应改为:

1
const user = await getUser();

2. 忘记返回 Promise

1
2
3
4
5
function getUser() {
fetch("/api/user").then(response => response.json());
}

getUser().then(console.log); // getUser() 返回 undefined

正确写法:

1
2
3
function getUser() {
return fetch("/api/user").then(response => response.json());
}

3. 在 then 中忘记 return

1
2
3
4
5
6
7
fetch("/api/user")
.then(response => {
response.json(); // 没有 return
})
.then(user => {
console.log(user); // undefined
});

正确写法:

1
2
3
fetch("/api/user")
.then(response => response.json())
.then(console.log);

4. 捕获后无意中吞掉错误

1
2
3
4
5
6
7
async function saveData() {
try {
await database.save();
} catch (error) {
console.log(error);
}
}

由于 catch 正常结束,调用者会认为 saveData() 已成功完成。如果调用者必须知道失败,应重新抛出:

1
2
3
4
5
6
7
8
async function saveData() {
try {
await database.save();
} catch (error) {
console.error("保存失败:", error);
throw error;
}
}

5. forEach 不会等待 async 回调

1
2
3
4
5
users.forEach(async user => {
await saveUser(user);
});

console.log("全部完成"); // 可能过早执行

需要串行时:

1
2
3
for (const user of users) {
await saveUser(user);
}

需要并行时:

1
await Promise.all(users.map(user => saveUser(user)));

十三、未处理的 Promise rejection

如果 rejected Promise 没有被处理,就会产生未处理拒绝:

1
2
3
4
5
async function run() {
throw new Error("失败");
}

run(); // 没有 await,也没有 catch

正确做法:

1
2
3
run().catch(error => {
console.error(error);
});

对于明确不等待结果的任务,也应处理其错误:

1
2
3
void sendAnalytics().catch(error => {
console.error("统计上报失败:", error);
});

void 表明调用者明确放弃结果,而 .catch() 负责避免未处理拒绝。

十四、完整的分层错误处理示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class HttpError extends Error {
constructor(status, message, options) {
super(message, options);
this.name = "HttpError";
this.status = status;
}
}

async function requestJson(url) {
let response;

try {
response = await fetch(url);
} catch (error) {
throw new Error("无法连接服务器", { cause: error });
}

if (!response.ok) {
throw new HttpError(
response.status,
`HTTP 请求失败:${response.status}`
);
}

try {
return await response.json();
} catch (error) {
throw new Error("服务器返回了无效 JSON", { cause: error });
}
}

async function getUser(id) {
try {
return await requestJson(`/api/users/${id}`);
} catch (error) {
throw new Error(`获取用户 ${id} 失败`, { cause: error });
}
}

async function renderUserPage(id) {
try {
const user = await getUser(id);
console.log("渲染用户:", user);
} catch (error) {
console.error("页面加载失败:", error.message);

let current = error.cause;
while (current) {
console.error("原始原因:", current.message);
current = current.cause;
}

console.log("显示错误提示页面");
} finally {
console.log("关闭加载动画");
}
}

renderUserPage(42).catch(error => {
console.error("未处理的顶层错误:", error);
});

错误传播路径可能是:

1
2
3
4
5
6
7
8
9
fetch 网络失败
↓
requestJson 转换为“无法连接服务器”
↓
getUser 补充“获取用户 42 失败”的业务上下文
↓
renderUserPage 捕获并展示错误页面
↓
finally 关闭加载动画

推荐的分层原则是:

  • 底层产生或转换技术错误。
  • 中间层补充业务上下文。
  • 最上层决定如何展示、重试或终止。
  • 不要在每一层都无意义地捕获并吞掉错误。

十五、Promise 与 async/await 对照

Promise 写法 async/await 写法
Promise.resolve(value) async 函数中 return value
Promise.reject(error) async 函数中 throw error
.then(value => ...) const value = await ...
.catch(error => ...) try/catch
.finally(() => ...) try/finally
回调返回普通值 当前链恢复为 fulfilled
回调抛出异常 当前链变成 rejected
回调返回 Promise 等待并采用它的最终状态

结论

Promise 是 JavaScript 异步结果的基础表示方式,async/await 是建立在 Promise 之上的控制流语法。最核心的规则是:

  1. async 函数一定返回 Promise。
  2. async 函数中的 return value 会产生 fulfilled Promise。
  3. async 函数中的 throw error 会产生 rejected Promise。
  4. await 遇到 fulfilled Promise,会得到其成功值。
  5. await 遇到 rejected Promise,会在当前位置抛出错误。
  6. Promise 链中的异常会跳过后续成功处理器,传播到最近的 .catch()。
  7. .catch() 或 catch 返回普通值意味着错误已经恢复;重新 throw 才会继续传播。
  8. try/catch 只能捕获真正被 await 的异步失败,不能自动捕获一个未等待的 Promise。
  9. 多个独立异步任务应优先用 Promise.all() 并发执行;需要收集全部结果时使用 Promise.allSettled()。
  10. 每条可能失败的异步调用链,最终都应该有明确的错误处理边界,避免未处理的 Promise rejection。

实际开发中,可以优先使用 async/await 编写主要业务流程,用 Promise.all() 等 Promise API 表达并发关系,并按照“底层抛出、中层补充上下文、顶层统一处理”的方式组织错误。


Promise 与 async/await:使用方式、错误传播与捕获
http://example.com/2026/08/07/Promise与async-await详解/
Author
John Doe
Posted on
August 7, 2026
Licensed under