JavaScript异步编程: 回调callback , Promise,then,catch,finally , async,await 笔记250810
JavaScript 异步编程全面解析:回调、Promise、async/await
一、异步编程基础
为什么需要异步?
- 单线程限制:JavaScript 是单线程语言
- 避免阻塞:防止耗时操作冻结界面
- 性能优化:充分利用 CPU 和 I/O 资源
- 用户体验:保持应用响应灵敏
同步 vs 异步
// 同步 - 阻塞执行
console.log("开始");
const result = loadDataSync(); // 阻塞主线程
console.log(result);
console.log("结束");
// 异步 - 非阻塞
console.log("开始");
loadDataAsync(result => console.log(result)); // 回调函数
console.log("结束"); // 立即执行
二、回调函数(Callback)
基本模式
function fetchData(callback) {
setTimeout(() => {
callback('数据加载完成');
}, 1000);
}
fetchData(result => {
console.log(result);
});
回调地狱问题
getUser(userId, user => {
getProfile(user.id, profile => {
getPosts(profile.id, posts => {
renderPosts(posts, () => {
notifyUser(() => {
// 更多嵌套...
});
});
});
});
});
解决方案
- 命名函数:拆分回调为独立函数
- 模块化:功能拆分为独立模块
- Promise:更先进的异步管理
三、Promise 核心机制
创建 Promise
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.5 ?
resolve('成功') :
reject(new Error('失败'));
}, 1000);
});
Promise 状态机
stateDiagram-v2
[*] --> Pending
Pending --> Fulfilled: resolve()
Pending --> Rejected: reject()
Fulfilled --> [*]
Rejected --> [*]
.then() - 处理成功/失败
promise.then(
value => console.log("成功:", value),
error => console.error("失败:", error)
);
.catch() - 错误处理
fetchData()
.then(processData)
.catch(error => {
console.error("请求失败:", error);
return fallbackData;
});
.finally() - 最终清理
let loading = true;
fetchData()
.then(render)
.catch(showError)
.finally(() => {
loading = false; // 无论成败都执行
cleanResources();
});
链式调用
getUser()
.then(user => getPosts(user.id))
.then(posts => filterPosts(posts))
.then(filtered => render(filtered))
.catch(handleError);
静态方法
| 方法 | 描述 | 示例 |
|---|---|---|
Promise.all() | 所有成功时返回结果 | Promise.all([p1, p2]) |
Promise.race() | 返回第一个完成的结果 | Promise.race([p1, p2]) |
Promise.any() | 返回第一个成功的结果 | Promise.any([p1, p2]) |
Promise.allSettled() | 所有完成后返回结果 | Promise.allSettled([p1, p2]) |
四、async/await 革命
基本用法
async function fetchUserData() {
try {
const user = await getUser();
const posts = await getPosts(user.id);
return { user, posts };
} catch (error) {
console.error("获取失败:", error);
return null;
} finally {
console.log("请求完成");
}
}
执行机制
sequenceDiagram
participant Main as 主线程
participant Async as async函数
participant Promise
Main->>Async: 调用异步函数
Async->>Async: 执行同步代码
Async->>Promise: await 暂停执行
Main->>Main: 继续执行其他代码
Promise-->>Async: Promise 解决
Async->>Async: 恢复执行
Async->>Main: 返回结果
优势
- 同步风格:代码线性执行,更易理解
- 错误处理:使用 try/catch 统一处理
- 变量共享:避免回调的嵌套作用域问题
- 调试友好:堆栈追踪更清晰
五、三种方案对比
| 特性 | 回调函数 | Promise | async/await |
|---|---|---|---|
| 可读性 | ❌ 差 | ✅ 中 | ✅✅ 优 |
| 错误处理 | ❌ 困难 | ✅ 链式.catch | ✅ try/catch |
| 嵌套问题 | ❌ 回调地狱 | ✅ 链式调用解决 | ✅ 完全解决 |
| 返回值 | ❌ 只能回调 | ✅ 链式传递 | ✅ 直接返回 |
| 流程控制 | ❌ 复杂 | ✅ Promise.all等 | ✅ 同步风格 |
| 调试 | ❌ 困难 | ✅ 一般 | ✅ 优秀 |
| 浏览器支持 | ✅ 所有 | ✅ ES6+ | ✅ ES2017+ |
六、混合使用模式
Promise 与 async/await 互操作
// Promise 中使用 async 函数
function processData() {
return fetchData()
.then(data => transformAsync(data)) // 调用async函数
.then(result => save(result));
}
async function transformAsync(data) {
const cleaned = await cleanData(data);
return enrichData(cleaned);
}
// async 函数中使用 Promise 链
async function complexOperation() {
const raw = await getRawData();
return process(raw)
.then(result => optimize(result))
.catch(error => fallback());
}
回调转 Promise
// 将回调式API转换为Promise
function promisify(fn) {
return (...args) => new Promise((resolve, reject) => {
fn(...args, (err, result) => {
if (err) reject(err);
else resolve(result);
});
});
}
// 使用示例
const readFileAsync = promisify(fs.readFile);
readFileAsync('file.txt', 'utf8').then(console.log);
七、最佳实践
1. 错误处理方案
// 方案1: try/catch (推荐)
async function fetchResource() {
try {
const data = await fetch('/api');
return process(data);
} catch (error) {
handleError(error);
return fallback;
}
}
// 方案2: .catch()
fetch('/api')
.then(process)
.catch(handleError);
2. 并行执行优化
// 顺序执行 (慢)
const user = await getUser();
const posts = await getPosts(user.id);
// 并行执行 (快)
const [user, posts] = await Promise.all([
getUser(),
getPosts() // 不依赖user
]);
3. 循环中的异步处理
// 错误: 顺序执行效率低
async function slowProcess(items) {
for (const item of items) {
await processItem(item);
}
}
// 正确: 并行处理
async function fastProcess(items) {
await Promise.all(items.map(item => processItem(item)));
}
4. 避免常见陷阱
// 陷阱1: 忘记await
async function update() {
const data = fetchData(); // ❌ 忘记await
save(data); // data是Promise对象
}
// 陷阱2: 不必要的await
async function getData() {
const data = await fetchData();
return await process(data); // ❌ 第二个await不必要
}
// 正确写法
async function getData() {
const data = await fetchData();
return process(data); // 直接返回Promise
}
八、实际应用场景
1. 用户登录流程
async function login(username, password) {
showLoader();
try {
const user = await authenticate(username, password);
const profile = await fetchProfile(user.id);
const preferences = await getPreferences(user.id);
return { user, profile, preferences };
} catch (error) {
if (error.code === 'TIMEOUT') {
return loadCachedProfile();
}
throw error;
} finally {
hideLoader();
}
}
2. 文件分块上传
async function uploadFile(file, onProgress) {
const CHUNK_SIZE = 1024 * 1024; // 1MB
const chunks = Math.ceil(file.size / CHUNK_SIZE);
for (let i = 0; i < chunks; i++) {
const chunk = file.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);
await uploadChunk(chunk, i);
onProgress(Math.round(((i + 1) / chunks) * 100));
}
return finalizeUpload(file.name);
}
3. 数据聚合处理
async function buildDashboard() {
// 并行启动所有数据请求
const [salesData, userStats, inventory] = await Promise.all([
fetchSalesData(),
getUserStatistics(),
getInventoryLevels()
]);
// 处理数据
const report = generateReport(salesData, userStats);
const stockAlert = checkInventory(inventory);
return { report, stockAlert };
}
九、调试技巧
1. 异步堆栈追踪
async function main() {
await step1();
}
async function step1() {
await step2();
}
async function step2() {
throw new Error('调试信息');
}
main().catch(console.error); // 完整堆栈追踪
2. Promise 状态检查
const promise = fetchData();
// 检查状态
console.log(promise); // Promise {<pending>}
promise
.then(() => console.log('成功'))
.catch(() => console.log('失败'));
3. 性能分析
console.time('API请求');
const data = await fetch('/api');
console.timeEnd('API请求'); // API请求: 256ms
十、演进历程与未来
JavaScript 异步演进史
graph LR
A(回调函数) --> B(Promise)
B --> C(Generator)
C --> D(async/await)
D --> E(异步迭代器)
E --> F(Web Workers)
现代异步模式
- 异步迭代器:
for await...of - 顶级 await:模块顶层使用 await
- Promise 组合器:
allSettled、any - Web Workers:真正的并行计算
总结
- 回调函数:基础异步模式,易导致回调地狱
- Promise:
.then()处理成功状态.catch()捕获错误.finally()最终清理- 链式调用解决回调嵌套
- async/await:
- 同步风格写异步代码
- try/catch 错误处理
- 最佳可读性和可维护性
黄金法则:
- 新项目首选 async/await
- 旧代码逐步迁移到 Promise/async
- 简单操作可用 Promise.then
- 复杂流程用 async/await
- 始终处理错误(try/catch 或 .catch())
掌握 JavaScript 异步编程,关键在于理解事件循环机制,并灵活选用适合的异步模式。async/await 作为现代 JavaScript 的核心特性,可大幅提升代码质量和开发效率。
JavaScript 异步编程完全指南:回调、Promise、async/await
一、异步编程的必要性 JavaScript 是单线程语言,异步编程解决了阻塞问题:
// 同步阻塞示例
console.log("开始同步操作");
const data = syncFetchData(); // 阻塞执行
console.log("结束同步操作");
// 异步非阻塞示例
console.log("开始异步操作");
asyncFetchData(result => {
console.log("收到结果:", result);
});
console.log("继续执行其他任务");
二、回调函数 (Callback) 基本模式
function fetchData(callback) {
setTimeout(() => {
callback({ id: 1, name: "数据" });
}, 1000);
}
fetchData(data => {
console.log("回调结果:", data);
});
回调地狱问题
getUser(userId, (user) => {
getOrders(user.id, (orders) => {
getOrderDetails(orders[0].id, (details) => {
renderUI(details, () => {
// 更多嵌套...
});
});
});
});
解决方案
- 命名函数:避免匿名嵌套
- 模块化:拆分功能
- 控制流库:如 Async.js
三、Promise 解决方案 创建 Promise
const promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
Math.random() > 0.5 ?
resolve("成功!") :
reject(new Error("失败!"));
}, 1000);
});
Promise 链式操作
fetch("/api/data")
.then(response => {
if (!response.ok) throw new Error("HTTP错误");
return response.json();
})
.then(data => processData(data))
.then(result => saveResult(result))
.catch(error => handleError(error))
.finally(() => cleanup());
Promise 方法对比
| 方法 | 描述 | 特点 |
|---|---|---|
then() | 处理成功状态 | 可链式调用 |
catch() | 处理错误状态 | 捕获链中所有错误 |
finally() | 最终执行 | 不改变结果值 |
Promise.all() | 所有成功 | 一个失败即失败 |
Promise.race() | 第一个完成 | 不考虑结果 |
Promise.any() | 第一个成功 | 全部失败才失败 |
Promise.allSettled() | 所有完成 | 包含成功/失败结果 |
四、async/await 语法糖 基本用法
async function fetchUserData() {
try {
const response = await fetch("/api/user");
const user = await response.json();
const orders = await fetchUserOrders(user.id);
return { user, orders };
} catch (error) {
console.error("请求失败:", error);
return null;
} finally {
console.log("请求完成");
}
}
并行优化
async function loadDashboard() {
const [user, posts] = await Promise.all([
fetchUser(),
fetchPosts()
]);
return { user, posts };
}
错误处理模式
// 方式1: try/catch
async function safeFetch() {
try {
return await fetch("/api");
} catch (error) {
return null;
}
}
// 方式2: catch方法
const data = await fetch("/api").catch(() => defaultData);
// 方式3: 结果元组
const [err, data] = await fetch("/api")
.then(data => [null, data])
.catch(err => [err, null]);
五、演进关系与转换 技术演进
回调函数 → Promise → Generator → async/await
回调转 Promise
function readFilePromise(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, (err, data) => {
err ? reject(err) : resolve(data);
});
});
}
// 使用
readFilePromise("data.txt")
.then(console.log)
.catch(console.error);
Promise 转 async/await
// Promise链
function process() {
return step1()
.then(step2)
.then(step3);
}
// async/await版
async function process() {
const result1 = await step1();
const result2 = await step2(result1);
return step3(result2);
}
六、异步模式应用场景 1. API请求序列
async function checkoutOrder() {
const cart = await getCart();
const address = await getShippingAddress();
const payment = await processPayment(cart.total);
await sendConfirmation(cart, address, payment);
}
2. 错误恢复
async function fetchWithRetry(url, retries = 3) {
try {
return await fetch(url);
} catch (err) {
if (retries <= 0) throw err;
await new Promise(r => setTimeout(r, 1000));
return fetchWithRetry(url, retries - 1);
}
}
3. 并发控制
async function runParallel(tasks, concurrency = 5) {
const results = [];
const running = [];
for (const task of tasks) {
const p = task().then(res => {
running.splice(running.indexOf(p), 1);
return res;
});
results.push(p);
running.push(p);
if (running.length >= concurrency) {
await Promise.race(running);
}
}
return Promise.all(results);
}
七、性能优化与陷阱规避 优化策略
-
避免顺序等待:
// 低效 const a = await fetchA(); const b = await fetchB(); // 高效 const [a, b] = await Promise.all([fetchA(), fetchB()]); -
缓存Promise:
const userCache = {}; function getUser(id) { if (!userCache[id]) { userCache[id] = fetch(`/user/${id}`).then(r => r.json()); } return userCache[id]; } -
流式处理:
async function processLargeFile() { const stream = getReadableStream(); for await (const chunk of stream) { await processChunk(chunk); } }
常见陷阱
| 陷阱 | 错误示例 | 解决方案 |
|---|---|---|
| 忘记await | saveData(data); | await saveData(data); |
| 循环中的await | items.forEach(async i => await p(i)); | for (const i of items) await p(i); |
| Promise未处理拒绝 | fetch('/api'); | 添加catch处理 |
| 资源泄漏 | 缺少finally清理 | 使用try/finally |
八、现代异步特性 1. 顶层 await (ES2022)
// 模块中直接使用
const config = await fetchConfig();
export default config;
2. Promise.withResolvers (ES2024)
const { promise, resolve, reject } = Promise.withResolvers();
document.getElementById("btn").onclick = resolve;
await promise;
3. 异步迭代器
async function* asyncGenerator() {
yield await fetchPage(1);
yield await fetchPage(2);
yield await fetchPage(3);
}
for await (const page of asyncGenerator()) {
renderPage(page);
}
九、总结与最佳实践 技术选型指南
| 场景 | 推荐方案 |
|---|---|
| 简单异步操作 | Promise.then/catch |
| 复杂异步流程 | async/await |
| 并行请求 | Promise.all |
| 事件处理 | Promise + 回调 |
| 流式数据 | 异步迭代器 |
最佳实践清单
- 优先使用 async/await:提高可读性
- 并行独立操作:使用 Promise.all
- 统一错误处理:try/catch 或 catch()
- 资源清理:使用 finally 块
- 避免阻塞:拆分CPU密集型任务
- 命名Promise:提高可读性
- 全局错误处理:
// 浏览器 window.addEventListener("unhandledrejection", e => { e.preventDefault(); console.error("未处理错误:", e.reason); }); // Node.js process.on("unhandledRejection", (reason) => { console.error("未处理拒绝:", reason); });
异步编程全景图
graph TD
A[异步操作] --> B{解决方案}
B --> C[回调函数]
B --> D[Promise]
B --> E[async/await]
C --> F[事件处理]
D --> G[链式调用]
E --> H[同步风格]
F --> I[简单场景]
G --> J[复杂流程]
H --> J
I --> K[可维护代码]
J --> K
掌握 JavaScript 异步编程是现代 Web 开发的核心技能。从基础的回调函数到现代 async/await,理解每种模式的适用场景和转换关系,能够帮助开发者编写更健壮、高效的应用程序。随着 ECMAScript 标准的演进,异步编程模式仍在不断发展,建议持续关注新的语言特性。
Callback回调函数 转 Promise
回调函数转 Promise 完全指南
在 JavaScript 中,将回调函数转换为 Promise 是现代化遗留代码的关键技术。以下是详细的转换方法和最佳实践:
基本转换模式
// 原始回调函数
function callbackStyle(arg, callback) {
// 异步操作
setTimeout(() => {
const result = `处理结果: ${arg}`;
callback(null, result); // Node.js 风格 (错误优先)
}, 100);
}
// 转换为 Promise
function promiseStyle(arg) {
return new Promise((resolve, reject) => {
callbackStyle(arg, (err, result) => {
if (err) reject(err); // 错误时拒绝
else resolve(result); // 成功时解决
});
});
}
// 使用示例
promiseStyle("输入数据")
.then(console.log) // 输出: "处理结果: 输入数据"
.catch(console.error);
Node.js 实用工具方法
Node.js 提供了内置的转换工具:
const util = require('util');
// 转换单个函数
const promiseVersion = util.promisify(callbackStyle);
// 转换整个模块
const fs = require('fs');
const readFileAsync = util.promisify(fs.readFile);
// 使用
async function readConfig() {
try {
const data = await readFileAsync('config.json', 'utf8');
return JSON.parse(data);
} catch (err) {
console.error('读取配置文件失败', err);
return defaultConfig;
}
}
浏览器环境实现
浏览器中没有内置的 promisify,可以自己实现:
function promisify(fn) {
return function(...args) {
return new Promise((resolve, reject) => {
// 添加自定义回调
fn(...args, (err, result) => {
if (err) reject(err);
else resolve(result);
});
});
};
}
// 使用示例
const setTimeoutAsync = promisify(setTimeout);
setTimeoutAsync(1000)
.then(() => console.log('1秒后执行'))
.catch(console.error);
处理特殊回调模式
1. 多参数回调
// 原始函数返回多个值
function multiArg(callback) {
setTimeout(() => callback(null, '结果1', '结果2'), 100);
}
// 转换方法
function promisifyMulti(fn) {
return function(...args) {
return new Promise((resolve, reject) => {
fn(...args, (err, ...results) => {
if (err) reject(err);
// 多个结果转为数组
else resolve(results.length > 1 ? results : results[0]);
});
});
};
}
// 使用
const asyncMulti = promisifyMulti(multiArg);
asyncMulti().then(([res1, res2]) => console.log(res1, res2));
2. 无错误参数的回调
// jQuery 风格的成功回调
function jqueryStyle(successCallback) {
setTimeout(() => successCallback('成功结果'), 100);
}
// 转换
function promisifyNoError(fn) {
return function(...args) {
return new Promise(resolve => {
fn(...args, resolve); // 直接使用 resolve
});
};
}
3. 事件发射器转换
// 事件发射器转 Promise
function eventToPromise(emitter, event) {
return new Promise((resolve, reject) => {
const successHandler = (...args) => {
cleanup();
resolve(args);
};
const errorHandler = (err) => {
cleanup();
reject(err);
};
function cleanup() {
emitter.off(event, successHandler);
emitter.off('error', errorHandler);
}
emitter.on(event, successHandler);
emitter.on('error', errorHandler);
});
}
// 使用示例
const http = require('http');
const server = http.createServer();
async function startServer(port) {
try {
server.listen(port);
await eventToPromise(server, 'listening');
console.log(`服务器已在端口 ${port} 启动`);
} catch (err) {
console.error('启动失败', err);
}
}
完整转换方案
/**
* 高级 promisify 函数
* 支持多种回调模式:
* 1. Node.js 风格 (err, result)
* 2. 多返回值 (err, res1, res2)
* 3. 仅成功回调
* 4. 自定义上下文
*/
function advancedPromisify(fn, context) {
return function(...args) {
return new Promise((resolve, reject) => {
// 创建自定义回调
const customCallback = (err, ...results) => {
if (err) return reject(err);
// 处理不同结果数量
switch (results.length) {
case 0: resolve(); break;
case 1: resolve(results[0]); break;
default: resolve(results);
}
};
// 应用上下文并调用函数
try {
fn.apply(context || this, [...args, customCallback]);
} catch (syncError) {
reject(syncError);
}
});
};
}
实际应用示例
1. 文件操作转换
const fs = require('fs');
// 手动转换
const readFile = (path) => new Promise((resolve, reject) => {
fs.readFile(path, 'utf8', (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
// 使用
async function processFiles() {
try {
const config = await readFile('config.json');
const template = await readFile('template.html');
return { config: JSON.parse(config), template };
} catch (err) {
console.error('文件处理失败', err);
throw err;
}
}
2. 数据库查询转换
// 旧式数据库接口
const database = {
query: (sql, callback) => {
// 模拟异步查询
setTimeout(() => {
if (sql.includes('SELECT')) {
callback(null, [{ id: 1, name: '示例数据' }]);
} else {
callback(new Error('无效查询'));
}
}, 100);
}
};
// 转换为 Promise 接口
database.queryAsync = (sql) => new Promise((resolve, reject) => {
database.query(sql, (err, results) => {
if (err) reject(err);
else resolve(results);
});
});
// 使用
async function getUserData(userId) {
try {
const rows = await database.queryAsync(`SELECT * FROM users WHERE id = ${userId}`);
return rows[0];
} catch (err) {
console.error('数据库错误', err);
return null;
}
}
最佳实践
-
统一错误处理:
// 创建带错误处理的统一封装 function createSafeAsync(fn) { return async (...args) => { try { return await fn(...args); } catch (err) { logError(err); // 统一错误日志 throw err; // 重新抛出 } }; } const safeReadFile = createSafeAsync(readFile); -
保持向后兼容:
// 同时提供回调和 Promise 接口 function dualInterface(arg, callback) { if (callback) { // 回调模式 legacyCallbackStyle(arg, callback); } else { // Promise 模式 return new Promise((resolve, reject) => { legacyCallbackStyle(arg, (err, res) => { err ? reject(err) : resolve(res); }); }); } } -
性能优化:
// 缓存 promisify 结果 const promisifyCache = new WeakMap(); function cachedPromisify(fn) { if (!promisifyCache.has(fn)) { promisifyCache.set(fn, promisify(fn)); } return promisifyCache.get(fn); }
转换流程图
传统回调函数
↓
new Promise 封装
↓
添加自定义回调函数
│
├── 成功 → resolve(结果)
│
└── 失败 → reject(错误)
↓
.then()/.catch()
↓
async/await 使用
通过以上方法,你可以将任何回调风格的函数转换为 Promise,从而:
- 与现代 async/await 语法兼容
- 避免回调地狱(Callback Hell)
- 使用 try/catch 统一错误处理
- 整合到 Promise 链和 Promise.all 等高级模式中
- 逐步重构旧代码库而不破坏现有功能
回调函数转 Promise:完整指南与最佳实践
回调函数与 Promise 的差异
回调函数模式
function callbackStyle(param, callback) {
// 异步操作
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
callback(null, `成功结果: ${param}`);
} else {
callback(new Error(`失败: ${param}`));
}
}, 1000);
}
// 使用回调
callbackStyle('数据', (error, result) => {
if (error) {
console.error(error.message);
return;
}
console.log(result);
});
Promise 模式
function promiseStyle(param) {
return new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve(`成功结果: ${param}`);
} else {
reject(new Error(`失败: ${param}`));
}
}, 1000);
});
}
// 使用 Promise
promiseStyle('数据')
.then(result => console.log(result))
.catch(error => console.error(error.message));
转换策略详解
1. Node.js 风格回调转换 (错误优先)
// 原始回调函数
function nodeStyleAsync(param, callback) {
// ...异步操作
if (error) callback(error);
else callback(null, result);
}
// 转换为 Promise
function promisifiedNodeStyle(param) {
return new Promise((resolve, reject) => {
nodeStyleAsync(param, (error, result) => {
if (error) reject(error);
else resolve(result);
});
});
}
2. 成功/失败分离回调转换
// 原始回调函数
function separateCallbacks(param, onSuccess, onError) {
// ...异步操作
if (success) onSuccess(result);
else onError(error);
}
// 转换为 Promise
function promisifiedSeparate(param) {
return new Promise((resolve, reject) => {
separateCallbacks(
param,
result => resolve(result),
error => reject(error)
);
});
}
3. 带多个参数的回调转换
// 原始回调函数
function multiParamCallback(param, callback) {
// ...异步操作
callback(null, data1, data2, data3);
}
// 转换为 Promise
function promisifiedMultiParam(param) {
return new Promise((resolve) => {
multiParamCallback(param, (error, ...results) => {
if (error) reject(error);
else resolve(results); // 返回数组 [data1, data2, data3]
});
});
}
自动转换工具
1. Node.js util.promisify
const util = require('util');
// Node.js 风格函数
const fs = require('fs');
const readFile = util.promisify(fs.readFile);
// 使用 Promise
async function readConfig() {
try {
const data = await readFile('config.json', 'utf8');
return JSON.parse(data);
} catch (error) {
console.error('读取配置失败:', error);
return {};
}
}
2. 自定义 promisify 函数
function promisify(original) {
return function(...args) {
return new Promise((resolve, reject) => {
original.call(this, ...args, (error, ...results) => {
if (error) return reject(error);
if (results.length <= 1) resolve(results[0]);
else resolve(results);
});
});
};
}
// 使用示例
const customReadFile = promisify(fs.readFile);
customReadFile('data.txt', 'utf8')
.then(data => console.log(data))
.catch(console.error);
高级转换模式
1. 回调超时处理
function withTimeout(promise, timeoutMs) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('操作超时')), timeoutMs)
)
]);
}
// 使用
withTimeout(promisifiedFunction(), 3000)
.then(console.log)
.catch(error => console.error(error.message));
2. 重试机制
function withRetry(asyncFn, retries = 3, delay = 1000) {
return new Promise((resolve, reject) => {
const attempt = (n) => {
asyncFn()
.then(resolve)
.catch(error => {
if (n <= 0) return reject(error);
setTimeout(() => attempt(n - 1), delay);
});
};
attempt(retries);
});
}
// 使用
withRetry(() => promisifiedUnstableAPI(), 5)
.then(console.log)
.catch(console.error);
3. 批处理回调
function batchPromisify(asyncFn, items) {
return Promise.all(items.map(item =>
new Promise((resolve, reject) => {
asyncFn(item, (error, result) => {
if (error) reject(error);
else resolve(result);
});
})
));
}
// 使用
const items = [1, 2, 3, 4, 5];
batchPromisify(callbackStyleFunction, items)
.then(results => console.log('批处理结果:', results))
.catch(console.error);
实际应用场景
1. 文件操作转换
// 回调版本
const fs = require('fs');
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
});
// Promise 版本
const { promisify } = require('util');
const readFile = promisify(fs.readFile);
async function readFiles() {
try {
const file1 = await readFile('file1.txt', 'utf8');
const file2 = await readFile('file2.txt', 'utf8');
console.log(file1, file2);
} catch (error) {
console.error('读取文件失败:', error);
}
}
2. 数据库操作转换
// MongoDB 回调风格
db.collection('users').findOne({ id: 123 }, (err, user) => {
if (err) return console.error(err);
console.log(user);
});
// Promise 版本
function findUser(id) {
return new Promise((resolve, reject) => {
db.collection('users').findOne({ id }, (err, user) => {
if (err) reject(err);
else resolve(user);
});
});
}
// 使用
findUser(123)
.then(console.log)
.catch(console.error);
3. HTTP 请求转换
// 传统 XMLHttpRequest 回调
function xhrRequest(url, callback) {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = () => callback(null, xhr.responseText);
xhr.onerror = () => callback(new Error('请求失败'));
xhr.send();
}
// Promise 版本
function fetchUrl(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = () => resolve(xhr.responseText);
xhr.onerror = () => reject(new Error('请求失败'));
xhr.send();
});
}
// 使用
fetchUrl('/api/data')
.then(data => console.log(JSON.parse(data)))
.catch(console.error);
最佳实践与陷阱规避
1. 避免常见错误
// 错误:未捕获异常
promisifiedFunction().then(console.log); // 缺少 catch
// 正确:始终处理错误
promisifiedFunction()
.then(console.log)
.catch(console.error);
// 错误:回调多次调用
function incorrectPromise() {
return new Promise((resolve, reject) => {
someAsync((err, data) => {
if (err) reject(err);
resolve(data); // 可能同时调用 resolve 和 reject
});
});
}
// 正确:确保只调用一次
function correctPromise() {
return new Promise((resolve, reject) => {
let resolved = false;
someAsync((err, data) => {
if (resolved) return;
resolved = true;
if (err) reject(err);
else resolve(data);
});
});
}
2. 上下文绑定
// 回调函数中使用 this
function Database(connection) {
this.connection = connection;
}
Database.prototype.query = function(sql, callback) {
// 使用 this.connection 执行查询
callback(null, `结果: ${sql}`);
};
// 转换时保持上下文
Database.prototype.promisifiedQuery = function(sql) {
return new Promise((resolve, reject) => {
this.query(sql, (error, result) => {
if (error) reject(error);
else resolve(result);
});
});
};
// 使用
const db = new Database('mysql://localhost');
db.promisifiedQuery('SELECT * FROM users')
.then(console.log)
.catch(console.error);
3. 性能优化
// 缓存 promisified 函数
const promisifyCache = new WeakMap();
function smartPromisify(fn) {
if (promisifyCache.has(fn)) {
return promisifyCache.get(fn);
}
const promisified = function(...args) {
return new Promise((resolve, reject) => {
fn.call(this, ...args, (error, ...results) => {
if (error) reject(error);
else resolve(results.length <= 1 ? results[0] : results);
});
});
};
promisifyCache.set(fn, promisified);
return promisified;
}
回调转 Promise 总结表
| 回调模式 | 转换方法 | 示例 |
|---|---|---|
| 错误优先回调 | 检查第一个参数 | (err, data) => err ? reject(err) : resolve(data) |
| 分离回调 | 分别处理成功/失败 | onSuccess: resolve, onError: reject |
| 多参数回调 | 返回数组或对象 | resolve([result1, result2]) |
| 上下文绑定 | 使用 .call(this) | original.call(this, ...args, callback) |
| 多次调用防护 | 添加调用标志 | if (called) return; called = true; |
| 批处理操作 | Promise.all + map | items.map(item => new Promise(...)) |
结论与迁移策略
转换步骤
- 识别回调模式:错误优先、分离回调或多参数
- 创建 Promise 包装器:使用
new Promise结构 - 处理回调结果:正确调用 resolve/reject
- 处理上下文:保持正确的
this绑定 - 添加错误处理:确保所有路径处理错误
- 优化性能:考虑批处理和缓存
迁移建议
- 逐步迁移:优先转换关键路径的代码
- 混合使用:在过渡期同时支持回调和 Promise
- 使用工具函数:如
util.promisify或自定义实现 - 添加类型支持:使用 TypeScript 定义 Promise 返回值
- 更新文档:明确标记已转换函数的接口变化
// 兼容模式:同时支持回调和 Promise
function dualInterface(param, callback) {
// 如果提供了回调,使用回调风格
if (typeof callback === 'function') {
asyncOperation(param, callback);
return;
}
// 否则返回 Promise
return new Promise((resolve, reject) => {
asyncOperation(param, (error, result) => {
if (error) reject(error);
else resolve(result);
});
});
}
// 使用
dualInterface('data', (err, res) => { /* 回调 */ });
dualInterface('data').then(/* Promise */);
通过掌握这些转换技术和最佳实践,您可以有效地将遗留的回调风格代码迁移到现代化的 Promise 和 async/await 模式,提高代码的可读性、可维护性和错误处理能力。
JavaScript 回调函数转 Promise 完全指南
在异步编程中,将回调函数转换为 Promise 是提升代码可读性和可维护性的关键技巧。本文将深入探讨回调转 Promise 的各种方法,并提供实际应用示例。
一、回调函数的问题与 Promise 的优势
回调地狱(Callback Hell)示例
getUser(userId, (user) => {
getOrders(user.id, (orders) => {
getOrderDetails(orders[0].id, (details) => {
renderUI(details, () => {
// 更多嵌套...
});
});
});
});
Promise 解决方案
getUser(userId)
.then(user => getOrders(user.id))
.then(orders => getOrderDetails(orders[0].id))
.then(renderUI)
.catch(handleError);
二、手动转换回调为 Promise
基本转换模式
function promisify(fn) {
return function(...args) {
return new Promise((resolve, reject) => {
// 添加回调函数作为最后一个参数
fn(...args, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
};
}
使用示例:文件读取
const fs = require('fs');
// 原始回调方式
fs.readFile('data.txt', 'utf8', (err, data) => {
if (err) console.error(err);
else console.log(data);
});
// 转换为Promise
const readFilePromise = promisify(fs.readFile);
readFilePromise('data.txt', 'utf8')
.then(console.log)
.catch(console.error);
三、Node.js 内置工具
util.promisify (Node.js 8+)
const util = require('util');
const fs = require('fs');
const readFile = util.promisify(fs.readFile);
async function processFile() {
try {
const data = await readFile('config.json', 'utf8');
return JSON.parse(data);
} catch (error) {
console.error('文件处理失败:', error);
return {};
}
}
自定义 promisify 方法
const promisify = (fn) =>
(...args) => new Promise((resolve, reject) =>
fn(...args, (err, ...results) =>
err ? reject(err) : resolve(results.length > 1 ? results : results[0])
)
);
四、高级转换技巧
1. 处理多个返回值
function getUserData(userId, callback) {
// 模拟返回多个值
setTimeout(() => callback(null, { name: 'John' }, 30, 'New York'), 100);
}
// 转换
const getUserDataPromise = promisify(getUserData);
getUserDataPromise(123)
.then(([user, age, city]) => {
console.log(`${user.name}, ${age}岁, 来自${city}`);
});
2. 转换对象方法
const database = {
connect: (options, callback) => {
setTimeout(() => callback(null, { status: 'connected' }), 500);
},
query: (sql, callback) => {
setTimeout(() => callback(null, [{ id: 1, name: 'John' }]), 300);
}
};
// 转换整个对象
function promisifyAll(obj) {
const result = {};
for (const key in obj) {
if (typeof obj[key] === 'function') {
result[key + 'Async'] = promisify(obj[key]);
}
}
return result;
}
const db = promisifyAll(database);
// 使用
db.connectAsync({ host: 'localhost' })
.then(() => db.queryAsync('SELECT * FROM users'))
.then(console.log);
五、浏览器环境中的转换
1. 转换 XMLHttpRequest
function fetchUrl(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = () => resolve(xhr.responseText);
xhr.onerror = () => reject(new Error('请求失败'));
xhr.send();
});
}
// 使用
fetchUrl('https://api.example.com/data')
.then(data => console.log(JSON.parse(data)))
.catch(console.error);
2. 转换事件监听器
function eventToPromise(element, eventName) {
return new Promise(resolve => {
const handler = (e) => {
element.removeEventListener(eventName, handler);
resolve(e);
};
element.addEventListener(eventName, handler);
});
}
// 使用
const button = document.getElementById('submit-btn');
async function handleClick() {
const event = await eventToPromise(button, 'click');
console.log('按钮被点击', event);
}
六、实际应用场景
1. 顺序执行异步操作
// 回调版本
function processData(input, callback) {
step1(input, (err, result1) => {
if (err) return callback(err);
step2(result1, (err, result2) => {
if (err) return callback(err);
step3(result2, callback);
});
});
}
// Promise 版本
async function processData(input) {
const result1 = await step1Promise(input);
const result2 = await step2Promise(result1);
return step3Promise(result2);
}
2. 并行执行异步操作
// 回调版本
function loadAllResources(callback) {
let count = 0;
const results = {};
let hasError = false;
function done(err, name, data) {
if (hasError) return;
if (err) {
hasError = true;
return callback(err);
}
results[name] = data;
if (++count === 3) callback(null, results);
}
loadResource('users', done);
loadResource('posts', done);
loadResource('comments', done);
}
// Promise 版本
async function loadAllResources() {
const [users, posts, comments] = await Promise.all([
loadResourcePromise('users'),
loadResourcePromise('posts'),
loadResourcePromise('comments')
]);
return { users, posts, comments };
}
3. 复杂工作流控制
async function userCheckout(userId) {
try {
const user = await getUser(userId);
const [cart, address] = await Promise.all([
getCart(user.id),
getAddress(user.id)
]);
if (cart.items.length === 0) {
throw new Error('购物车为空');
}
const payment = await processPayment(user, cart.total);
await sendConfirmationEmail(user, payment);
return { success: true, orderId: payment.orderId };
} catch (error) {
console.error('结账失败:', error);
return { success: false, error: error.message };
}
}
七、错误处理最佳实践
1. 错误类型区分
class DatabaseError extends Error {
constructor(message) {
super(message);
this.name = 'DatabaseError';
}
}
// 转换时添加错误类型
function promisify(fn) {
return (...args) => new Promise((resolve, reject) => {
fn(...args, (error, result) => {
if (error) {
reject(new DatabaseError(error.message));
} else {
resolve(result);
}
});
});
}
// 使用
try {
const data = await dbQuery('SELECT * FROM users');
} catch (error) {
if (error instanceof DatabaseError) {
console.error('数据库错误:', error);
// 执行恢复操作
} else {
throw error;
}
}
2. 错误重试机制
function withRetry(fn, retries = 3, delay = 1000) {
return async function(...args) {
let lastError;
for (let i = 0; i < retries; i++) {
try {
return await fn(...args);
} catch (error) {
lastError = error;
console.warn(`尝试 ${i+1}/${retries} 失败,${delay}ms后重试`);
await new Promise(resolve => setTimeout(resolve, delay));
delay *= 2; // 指数退避
}
}
throw lastError;
};
}
// 使用
const reliableFetch = withRetry(promisify(unreliableApiCall), 5);
八、性能优化与注意事项
1. Promise 内存管理
async function processLargeDataset() {
const stream = getDataStream();
let processed = 0;
for await (const chunk of stream) {
const result = await processChunk(chunk);
await saveResult(result);
processed++;
if (processed % 1000 === 0) {
// 手动触发垃圾回收(仅Node.js)
if (global.gc) global.gc();
}
}
}
2. 避免 Promise 内存泄漏
// 错误示例:未处理的Promise
function leakyFunction() {
// 忘记处理返回的Promise
someAsyncOperation();
}
// 正确做法
async function safeFunction() {
try {
await someAsyncOperation();
} catch (error) {
console.error('操作失败', error);
}
}
3. 回调转 Promise 的局限性
| 场景 | 问题 | 解决方案 |
|---|---|---|
| 多次回调 | 如 EventEmitter | 使用事件流或特殊转换 |
| 进度报告 | Promise 不支持进度 | 使用回调+Promise 混合 |
| 取消操作 | 原生 Promise 不可取消 | 使用 AbortController |
| 返回多个值 | resolve 只接受单参数 | 返回对象或数组 |
九、现代化替代方案
1. 使用 async/await
// 基于回调的函数
function oldApi(param, callback) { /* ... */ }
// 包装为Promise
const newApi = promisify(oldApi);
// 使用async/await
async function modernUsage() {
try {
const result = await newApi('parameter');
console.log('结果:', result);
} catch (error) {
console.error('错误:', error);
}
}
2. 使用 async_hooks (Node.js)
const async_hooks = require('async_hooks');
const { promisify } = require('util');
// 创建异步上下文
const asyncContext = new Map();
const hook = async_hooks.createHook({
init: (asyncId, type, triggerAsyncId) => {
// 继承父级上下文
if (asyncContext.has(triggerAsyncId)) {
asyncContext.set(asyncId, asyncContext.get(triggerAsyncId));
}
},
destroy: (asyncId) => {
asyncContext.delete(asyncId);
}
});
hook.enable();
// 增强版promisify
function contextPromisify(fn) {
return (...args) => {
const context = { id: Date.now() };
return new Promise((resolve, reject) => {
const asyncId = async_hooks.executionAsyncId();
asyncContext.set(asyncId, context);
fn(...args, (err, result) => {
if (err) reject(err);
else resolve({ result, context });
});
});
};
}
十、总结与最佳实践
回调转 Promise 的完整流程
graph TD
A[识别回调函数] --> B{判断回调模式}
B -->|错误优先| C[使用 promisify]
B -->|其他模式| D[自定义转换逻辑]
C --> E[处理多个返回值]
D --> F[处理特殊场景]
E --> G[添加错误类型]
F --> G
G --> H[测试转换结果]
H --> I[集成到应用]
最佳实践清单
- 优先使用 util.promisify(Node.js 环境)
- 统一错误处理:使用 try/catch 或 .catch()
- 保持上下文:使用 bind 或箭头函数
- 处理多个返回值:返回对象或数组
- 添加超时控制:避免永久挂起
function withTimeout(promise, ms) { return Promise.race([ promise, new Promise((_, reject) => setTimeout(() => reject(new Error('超时')), ms) ]); } - 测试转换结果:确保完整功能
- 文档化接口:说明返回的 Promise 特性
回调转 Promise 的价值
- 代码可读性:消除回调嵌套
- 错误处理:统一使用 try/catch
- 流程控制:简化复杂异步逻辑
- 现代特性:兼容 async/await 语法
- 可维护性:更易重构和扩展
通过将回调函数转换为 Promise,您可以显著提升 JavaScript 代码的质量和可维护性。这一技巧在现代 JavaScript 开发中至关重要,尤其是在处理遗留代码或与旧 API 交互时。
new Promise().then().catch().finally() , async{await}
JavaScript 异步编程:Promise 链与 async/await 深度解析
核心概念对比
Promise 链式操作
new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => resolve("初始数据"), 1000);
})
.then(result => {
console.log("第一步:", result);
return result + " -> 处理后";
})
.then(result => {
console.log("第二步:", result);
return new Promise(resolve =>
setTimeout(() => resolve(result + " -> 完成"), 500)
);
})
.catch(error => {
console.error("捕获错误:", error);
})
.finally(() => {
console.log("清理操作");
});
async/await 操作
async function processData() {
try {
// 相当于 new Promise()
let result = await new Promise(resolve =>
setTimeout(() => resolve("初始数据"), 1000)
);
console.log("第一步:", result);
result += " -> 处理后";
console.log("第二步:", result);
// 相当于 .then()
result = await new Promise(resolve =>
setTimeout(() => resolve(result + " -> 完成"), 500)
);
return result;
} catch (error) {
console.error("捕获错误:", error);
throw error;
} finally {
console.log("清理操作");
}
}
processData().then(finalResult => {
console.log("最终结果:", finalResult);
});
核心概念详解
1. Promise 构造函数
const promise = new Promise((resolve, reject) => {
// 执行异步操作
if (success) {
resolve(value); // 状态变为 fulfilled
} else {
reject(error); // 状态变为 rejected
}
});
2. then() 方法
- 接收两个可选参数:
onFulfilled和onRejected - 返回新 Promise 对象,支持链式调用
promise.then(
value => {
// 处理成功状态
return newValue;
},
error => {
// 处理失败状态(较少使用)
return recoveryValue;
}
);
3. catch() 方法
- 专门处理 rejected 状态
- 相当于
.then(null, errorHandler)
promise
.then(handleSuccess)
.catch(handleError); // 捕获链中所有错误
4. finally() 方法
- 无论成功失败都会执行
- 不接收任何参数
- 通常用于清理操作
promise
.then(handleSuccess)
.catch(handleError)
.finally(cleanup); // 总是执行
5. async 函数
- 声明异步函数
- 总是返回 Promise 对象
- 可以使用 await 关键字
async function fetchData() {
return "数据"; // 等同于 return Promise.resolve("数据")
}
6. await 表达式
- 只能在 async 函数中使用
- 暂停函数执行,等待 Promise 解决
- 返回 Promise 的解决值
async function getData() {
const data = await fetch('/api'); // 等待Promise解决
return process(data);
}
两种风格转换指南
1. 基本转换 Promise 链 → async/await
// Promise 链
function getData() {
return fetchData()
.then(process)
.then(save);
}
// async/await 版本
async function getData() {
const data = await fetchData();
const processed = await process(data);
return await save(processed);
}
2. 错误处理转换
// Promise 链
function fetchResource() {
return fetch('/resource')
.then(handleResponse)
.catch(error => {
console.error('请求失败', error);
return defaultValue;
});
}
// async/await 版本
async function fetchResource() {
try {
const response = await fetch('/resource');
return await handleResponse(response);
} catch (error) {
console.error('请求失败', error);
return defaultValue;
}
}
3. finally 转换
// Promise 链
function transaction() {
return beginTransaction()
.then(executeOperation)
.then(commit)
.catch(rollback)
.finally(endTransaction);
}
// async/await 版本
async function transaction() {
try {
await beginTransaction();
const result = await executeOperation();
await commit();
return result;
} catch (error) {
await rollback();
throw error;
} finally {
await endTransaction();
}
}
4. 并行操作转换
// Promise 链
function loadDashboard() {
return Promise.all([
fetchUser(),
fetchOrders(),
fetchMessages()
]).then(([user, orders, messages]) => {
return { user, orders, messages };
});
}
// async/await 版本
async function loadDashboard() {
const [user, orders, messages] = await Promise.all([
fetchUser(),
fetchOrders(),
fetchMessages()
]);
return { user, orders, messages };
}
混合使用模式
1. 在 async 函数中使用 Promise 链
async function complexOperation() {
const data = await fetchInitialData();
// 对部分操作使用Promise链
return processPart1(data)
.then(processPart2)
.then(processPart3)
.catch(handlePartialError);
}
2. 在 Promise 链中使用 async 函数
function dataPipeline() {
return getRawData()
.then(async rawData => {
const cleaned = await cleanData(rawData);
return transformData(cleaned);
})
.then(finalize)
.catch(handleError);
}
执行机制对比
Promise 链执行流程
console.log("开始");
new Promise(resolve => {
console.log("Promise 执行器");
setTimeout(() => resolve("结果"), 0);
})
.then(result => console.log("then:", result))
.finally(() => console.log("finally"));
console.log("结束");
/* 输出:
开始
Promise 执行器
结束
then: 结果
finally
*/
async/await 执行流程
console.log("开始");
async function demo() {
console.log("函数开始");
const result = await new Promise(resolve =>
setTimeout(() => resolve("结果"), 0)
);
console.log("await 结果:", result);
console.log("函数结束");
}
demo();
console.log("结束");
/* 输出:
开始
函数开始
结束
await 结果: 结果
函数结束
*/
错误处理差异
Promise 链错误传播
function example() {
return Promise.resolve()
.then(() => {
throw new Error("步骤1错误");
})
.then(() => {
console.log("这步不会执行");
})
.catch(error => {
console.error("捕获错误:", error.message); // 捕获错误: 步骤1错误
});
}
async/await 错误传播
async function example() {
try {
await Promise.resolve();
throw new Error("步骤1错误");
console.log("这步不会执行");
} catch (error) {
console.error("捕获错误:", error.message); // 捕获错误: 步骤1错误
}
}
性能与最佳实践
1. 避免不必要的 await
// 不推荐 (顺序执行)
const user = await getUser();
const posts = await getPosts(); // 等待getUser完成
// 推荐 (并行执行)
const [user, posts] = await Promise.all([getUser(), getPosts()]);
2. 合理使用 try/catch 范围
// 不推荐 - 过大的try块
try {
const a = await step1();
const b = await step2(a);
const c = await step3(b);
return c;
} catch (error) {
// 难以确定错误来源
}
// 推荐 - 精细错误处理
try {
const a = await step1();
try {
const b = await step2(a);
return await step3(b);
} catch (error) {
// 处理step2/step3错误
}
} catch (error) {
// 处理step1错误
}
3. 重要清理使用 finally
let resource;
try {
resource = await acquireResource();
await useResource(resource);
} finally {
if (resource) {
await releaseResource(resource); // 确保资源释放
}
}
浏览器兼容性解决方案
1. 不支持 Promise 的环境
<!-- 引入 polyfill -->
<script src="https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js"></script>
2. 不支持 async/await 的环境
// 使用 Babel 转译
// .babelrc 配置
{
"presets": ["@babel/preset-env"]
}
3. 回退方案
// 检查是否支持 async/await
if (typeof async function() {}.constructor === "function") {
// 使用现代语法
} else {
// 使用 Promise 或回调
}
总结:何时选择哪种方式
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 简单链式操作 | Promise 链 | 简洁直接 |
| 复杂异步逻辑 | async/await | 可读性更好 |
| 并行操作 | Promise.all + await | 高效简洁 |
| 错误恢复 | async/await + try/catch | 更精细控制 |
| 资源清理 | finally (两者都适用) | 确保执行 |
| 浏览器兼容 | Promise + polyfill | 更广泛支持 |
现代 JavaScript 最佳实践:
- 主要逻辑使用 async/await 提高可读性
- 并行操作使用 Promise.all() 优化性能
- 始终使用 try/catch 或 .catch() 处理错误
- 资源清理使用 finally 确保执行
- 旧环境使用 Babel 转译和 polyfill
通过掌握 Promise 链和 async/await 两种风格及其转换,你可以在不同场景中选择最合适的异步编程方式,编写出更健壮、可维护的 JavaScript 代码。
JavaScript 异步编程核心:Promise 与 async/await 详解
1. Promise 核心概念 Promise 是异步操作的容器,有三种状态:
- Pending(进行中)
- Fulfilled(已成功)
- Rejected(已失败)
// 创建 Promise
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.5 ?
resolve("成功! 🎉") :
reject(new Error("失败! ❌"));
}, 1000);
});
2. Promise 方法链
// 完整方法链
promise
.then(result => {
console.log("Step 1:", result);
return result + " 处理数据"; // 传递给下一步
})
.then(processed => {
console.log("Step 2:", processed);
throw new Error("中途出错"); // 模拟错误
})
.catch(error => {
console.error("捕获错误:", error.message);
return "错误恢复数据"; // 从错误中恢复
})
.finally(() => {
console.log("清理资源 - 总会执行 ♻️");
})
.then(finalResult => {
console.log("最终结果:", finalResult);
});
/* 可能的输出:
Step 1: 成功! 🎉
Step 2: 成功! 🎉 处理数据
捕获错误: 中途出错
清理资源 - 总会执行 ♻️
最终结果: 错误恢复数据
*/
3. async/await - Promise 的语法糖
// 基本用法
async function fetchData() {
try {
const result = await promise; // 等待 Promise 解决
const processed = await process(result); // 串行操作
return processed;
} catch (error) {
console.error("Async错误:", error.message);
return "默认数据";
} finally {
console.log("Async清理完成");
}
}
// 使用 async 函数
fetchData().then(console.log);
4. 关键特性对比
| 方法 | 作用 | 返回值 | 使用场景 |
|---|---|---|---|
.then() | 处理成功状态 | 新 Promise | 链式操作中的数据处理 |
.catch() | 捕获拒绝状态/错误 | 新 Promise | 错误处理 |
.finally() | 无论成功失败都执行 | 新 Promise | 资源清理(关闭文件/连接) |
await | 暂停 async 函数执行等待 Promise 解决 | Promise 解决值 | 替代 .then() 的同步写法 |
async | 声明异步函数 | 自动包装为 Promise | 包含 await 操作的函数 |
5. 最佳实践示例
// 1. 并行请求优化
async function loadDashboard() {
const [user, orders, notifications] = await Promise.all([
fetchUser(),
fetchOrders(),
fetchNotifications()
]);
return { user, orders, notifications };
}
// 2. 错误处理策略
async function safeOperation() {
try {
const data = await riskyFetch();
return data;
} catch (err) {
if (err instanceof NetworkError) {
return cachedData; // 网络错误使用缓存
}
throw err; // 其他错误重新抛出
}
}
// 3. 取消超时请求
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, { signal: controller.signal });
clearTimeout(timeoutId);
return response.json();
} catch (err) {
if (err.name === 'AbortError') throw new Error(`请求超时 (${timeout}ms)`);
throw err;
}
}
// 4. 循环中的并发控制
async function processBatch(items, concurrency = 5) {
const results = [];
for (let i = 0; i < items.length; i += concurrency) {
const batch = items.slice(i, i + concurrency);
const batchResults = await Promise.all(
batch.map(item => processItem(item))
);
results.push(...batchResults);
}
return results;
}
6. 常见陷阱与解决方案
-
忘记 await
// ❌ 错误:忘记 await async function saveData() { const data = fetchData(); // 返回 Promise 而非实际数据 store.save(data); // 保存的是 Promise 对象 } // ✅ 正确 async function saveData() { const data = await fetchData(); // 等待实际数据 store.save(data); } -
循环中的串行执行
// ❌ 低效:顺序执行 for (const url of urls) { await fetch(url); // 每次等待完成 } // ✅ 高效:并行执行 await Promise.all(urls.map(url => fetch(url))); -
忽略错误处理
// ❌ 危险:未处理可能的拒绝 async function main() { const data = await fetchData(); render(data); } // ✅ 安全:添加错误处理 async function main() { try { const data = await fetchData(); render(data); } catch (err) { showErrorUI(err); } } -
资源泄漏
// ❌ 危险:可能未关闭连接 async function queryDB() { const db = await connect(); const data = await db.query("SELECT..."); return data; // 忘记关闭连接! } // ✅ 安全:使用 finally 确保清理 async function queryDB() { const db = await connect(); try { return await db.query("SELECT..."); } finally { await db.close(); // 始终执行 } }
7. 高级模式
// 1. 重试机制
async function retry(fn, retries = 3, delay = 1000) {
try {
return await fn();
} catch (err) {
if (retries === 0) throw err;
await new Promise(r => setTimeout(r, delay));
return retry(fn, retries - 1, delay * 2); // 指数退避
}
}
// 2. 异步互斥锁
class AsyncLock {
constructor() {
this.queue = Promise.resolve();
}
acquire() {
let release;
const newPromise = new Promise(r => release = r);
const next = this.queue.then(() => release);
this.queue = newPromise;
return next;
}
}
// 使用锁
const lock = new AsyncLock();
async function safeUpdate() {
const release = await lock.acquire();
try {
await criticalSection();
} finally {
release();
}
}
总结流程图
创建 Promise
│
▼
┌── Pending ──┐
│ │
Resolve() Reject()
│ │
▼ ▼
then()处理 ◄─── catch()处理
│ │
└──► finally() ◄──┘
│
▼
async/await 封装
│
try/catch/finally
掌握这些核心模式和最佳实践,你将能:
- 优雅处理复杂异步流程
- 编写可维护的异步代码
- 避免常见陷阱和性能问题
- 实现高级异步控制模式
- 提升应用稳定性和用户体验
promise , then , catch , finally
JavaScript Promise 完全指南:then、catch、finally 详解
一、Promise 核心概念
什么是 Promise? Promise 是 JavaScript 中处理异步操作的对象,它表示一个尚未完成但预期会完成的操作,解决了传统的回调地狱问题。
Promise 的三种状态
stateDiagram-v2
[*] --> Pending
Pending --> Fulfilled: resolve(value)
Pending --> Rejected: reject(reason)
Fulfilled --> [*]
Rejected --> [*]
创建 Promise
const promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
if (Math.random() > 0.5) {
resolve('操作成功!'); // 状态变为 Fulfilled
} else {
reject(new Error('操作失败')); // 状态变为 Rejected
}
}, 1000);
});
二、.then() 方法详解
基本用法
promise.then(
value => console.log('成功:', value), // Fulfilled 回调
error => console.error('失败:', error) // Rejected 回调(可选)
);
链式调用
fetchData()
.then(response => response.json()) // 第一步
.then(data => process(data)) // 第二步
.then(result => save(result)) // 第三步
.catch(handleError); // 错误处理
链式调用规则
| 回调返回值类型 | 下一个 .then() 接收 |
|---|---|
| 普通值 | 作为参数传递给下一个 .then() |
| Promise 对象 | 等待该 Promise 解决后再传递 |
| 抛出错误 | 触发后续 .catch() |
三、.catch() 方法详解
错误处理
fetchData()
.then(process)
.catch(error => {
console.error('捕获错误:', error);
return fallbackValue; // 返回新值继续链式调用
});
错误传播特性
getUser()
.then(user => getPosts(user.id)) // 此处可能出错
.then(posts => render(posts)) // 此处可能出错
.catch(error => { // 捕获所有错误
showError(error);
});
与 try/catch 的对比
// Promise 方式
fetchData()
.then(process)
.catch(handleError);
// async/await 方式
async function fetchAndProcess() {
try {
const data = await fetchData();
return process(data);
} catch (error) {
handleError(error);
}
}
四、.finally() 方法详解
基本用法
let isLoading = true;
fetchData()
.then(render)
.catch(showError)
.finally(() => {
isLoading = false; // 无论成功失败都会执行
cleanResources();
});
关键特性
- 不接收参数:无法知道 Promise 的最终状态
- 不影响结果:传递原始 Promise 的结果
- 总是执行:无论成功或失败都会调用
执行顺序示例
Promise.resolve('原始值')
.finally(() => console.log('finally 执行'))
.then(value => console.log('then 接收:', value));
// 输出:
// finally 执行
// then 接收: 原始值
五、完整链式调用示例
完整工作流
function fetchUserData(userId) {
let loading = true;
return fetch(`/users/${userId}`)
.then(response => {
if (!response.ok) throw new Error('网络错误');
return response.json();
})
.then(user => {
console.log('用户数据:', user);
return fetch(`/posts?user=${userId}`);
})
.then(response => response.json())
.catch(error => {
console.error('请求失败:', error);
return getCachedData(userId); // 错误恢复
})
.finally(() => {
loading = false;
console.log('请求完成');
});
}
// 使用
fetchUserData(123)
.then(data => displayData(data));
六、静态方法详解
Promise.all()
// 所有Promise成功时返回结果数组
Promise.all([
fetchUser(),
fetchPosts(),
fetchComments()
])
.then(([user, posts, comments]) => {
renderDashboard(user, posts, comments);
})
.catch(error => {
console.log('至少一个请求失败', error);
});
Promise.race()
// 返回第一个完成的Promise(无论成功/失败)
Promise.race([
fetchData(),
timeout(5000) // 超时控制
])
.then(data => process(data))
.catch(error => {
if (error.message === '超时') {
useCachedData();
}
});
其他静态方法
| 方法 | 描述 | 使用场景 |
|---|---|---|
Promise.resolve() | 创建已解决的Promise | Promise.resolve('值') |
Promise.reject() | 创建已拒绝的Promise | Promise.reject(new Error()) |
Promise.allSettled() | 所有完成后返回结果 | 需要知道每个Promise的最终状态 |
Promise.any() | 返回第一个成功的Promise | 多个备用数据源 |
七、最佳实践
1. 错误处理策略
// 推荐方式:链尾统一捕获
fetchData()
.then(step1)
.then(step2)
.catch(handleError); // 捕获所有错误
// 特定步骤错误处理
fetchData()
.then(step1, handleStep1Error) // 仅处理step1错误
.then(step2)
.catch(handleOtherErrors); // 处理其他错误
2. 避免常见陷阱
// 错误:忘记返回Promise
fetchData()
.then(response => {
process(response); // ❌ 忘记return
});
// 正确:返回结果
fetchData()
.then(response => {
return process(response); // ✅
});
3. 资源清理模式
function withResource(resource) {
return fetchData()
.then(process)
.finally(() => {
resource.release(); // 确保资源释放
});
}
八、实际应用场景
1. 用户登录流程
function login(username, password) {
showLoader();
return authenticate(username, password)
.then(user => fetchProfile(user.id))
.then(profile => loadPreferences(profile.id))
.catch(error => {
showErrorMessage(error);
return retryLogin();
})
.finally(() => {
hideLoader();
});
}
2. 文件上传
function uploadFile(file, onProgress) {
const formData = new FormData();
formData.append('file', file);
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.upload.onprogress = e => {
if (e.lengthComputable) {
onProgress(Math.round((e.loaded / e.total) * 100));
}
};
xhr.onload = () => resolve(xhr.response);
xhr.onerror = () => reject(new Error('上传失败'));
xhr.open('POST', '/upload');
xhr.send(formData);
});
}
// 使用
uploadFile(file, updateProgressBar)
.then(showSuccess)
.catch(showError);
3. 数据加载与缓存
function loadDataWithCache(url) {
// 先尝试从缓存获取
return getCachedData(url)
.catch(() => {
// 缓存失败则从网络加载
return fetch(url)
.then(response => response.json())
.then(data => {
cacheData(url, data); // 缓存结果
return data;
});
})
.finally(updateLoadingState);
}
九、与 async/await 结合使用
在 async 函数中使用 Promise
async function getUserPosts(userId) {
try {
const user = await getUser(userId);
const posts = await getPosts(user.id);
return { user, posts };
} catch (error) {
console.error('加载失败:', error);
return loadCachedPosts();
} finally {
trackAnalytics();
}
}
在 Promise 链中使用 async
function processData() {
return fetchData()
.then(data => transformAsync(data)) // 调用async函数
.then(result => save(result));
}
async function transformAsync(data) {
const cleaned = await cleanData(data);
return enrichData(cleaned);
}
十、总结
Promise 方法对比表
| 方法 | 接收参数 | 返回值决定 | 状态传递 | 使用场景 |
|---|---|---|---|---|
.then() | 值/错误 | 回调返回值 | 可改变 | 主要操作流程 |
.catch() | 错误对象 | 回调返回值 | 可修复 | 错误处理 |
.finally() | 无 | 忽略 | 保持原始 | 资源清理 |
最佳实践指南
- 优先使用
.then()链:处理主要异步流程 - 链尾使用
.catch():统一处理错误 - 合理使用
.finally():执行清理操作 - 避免嵌套:保持链式调用扁平化
- 结合 async/await:复杂逻辑中使用同步风格
黄金法则
"Promise 是 JavaScript 异步编程的基石,掌握
.then()、.catch()和.finally()的使用,能够让你编写出更健壮、更易维护的异步代码。"
通过深入理解 Promise 及其方法,您将能够:
- 优雅处理异步操作
- 避免回调地狱
- 实现更可靠的错误处理
- 编写更清晰、更易维护的代码
- 构建高性能的 JavaScript 应用
async , await
JavaScript async/await 详解:现代异步编程的终极解决方案
一、async/await 的本质
async/await 是 ES2017 (ES8) 引入的 JavaScript 特性,它们建立在 Promise 之上,提供了更简洁、更直观的方式来处理异步操作:
// async 函数声明
async function fetchData() {
// await 暂停执行,等待 Promise 解决
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
}
// 使用 async 函数
fetchData()
.then(data => console.log(data))
.catch(error => console.error(error));
核心概念:
async:声明异步函数,隐式返回 Promiseawait:暂停函数执行,等待 Promise 解决- 错误处理:使用 try/catch 捕获异步错误
二、async 函数详解
1. 基本特性
// 1. 总是返回 Promise
async function example() {
return 42; // 等价于 Promise.resolve(42)
}
// 2. 抛出错误会返回拒绝的 Promise
async function fail() {
throw new Error('失败'); // 等价于 Promise.reject(new Error('失败'))
}
// 3. 返回 Promise 会直接返回该 Promise
async function getData() {
return fetch('/api/data'); // 直接返回 fetch 的 Promise
}
2. 执行流程
console.log('开始执行');
async function asyncFunc() {
console.log('async 函数开始');
const result = await new Promise(resolve =>
setTimeout(() => resolve('完成!'), 1000)
);
console.log(result);
return '函数结束';
}
asyncFunc().then(console.log);
console.log('继续执行');
/* 输出顺序:
开始执行
async 函数开始
继续执行
(1秒后) 完成!
函数结束
*/
三、await 表达式详解
1. 基本用法
async function process() {
// 等待 Promise 解决
const value = await somePromise;
// 等待普通值 (自动包装为 resolved Promise)
const num = await 42;
// 等待异步函数
const data = await fetchData();
}
2. 错误处理
async function loadResource() {
try {
const response = await fetch('/api/resource');
if (!response.ok) {
throw new Error('网络响应错误');
}
return await response.json();
} catch (error) {
console.error('加载失败:', error);
return { defaultValue: true };
} finally {
console.log('清理资源');
}
}
3. 并行执行优化
// 低效:顺序执行
async function slow() {
const a = await fetchA(); // 等待完成
const b = await fetchB(); // 再开始
return [a, b];
}
// 高效:并行执行
async function fast() {
// 同时启动所有请求
const promiseA = fetchA();
const promiseB = fetchB();
// 等待所有完成
const [a, b] = await Promise.all([promiseA, promiseB]);
return [a, b];
}
四、async/await 与 Promise 的关系
1. 转换规则
| Promise 代码 | async/await 代码 |
|---|---|
promise.then(onFulfilled) | const result = await promise |
promise.catch(onRejected) | try/catch 块 |
promise.finally() | finally 块 |
| 链式调用 | 顺序 await 语句 |
2. 相互转换示例
// Promise 链 → async/await
function getUserPosts(userId) {
return getUser(userId)
.then(user => getPosts(user.id))
.then(posts => filterPosts(posts));
}
async function getUserPosts(userId) {
const user = await getUser(userId);
const posts = await getPosts(user.id);
return filterPosts(posts);
}
// async/await → Promise
async function calculateTotal() {
const a = await getValueA();
const b = await getValueB();
return a + b;
}
function calculateTotal() {
return getValueA()
.then(a => getValueB()
.then(b => a + b));
}
五、高级用法与模式
1. 循环中的异步处理
// 顺序执行
async function processSequentially(items) {
for (const item of items) {
await processItem(item);
}
}
// 并行执行
async function processInParallel(items) {
await Promise.all(items.map(item => processItem(item)));
}
// 控制并发数
async function processWithConcurrency(items, max = 5) {
const results = [];
for (let i = 0; i < items.length; i += max) {
const chunk = items.slice(i, i + max);
const chunkResults = await Promise.all(chunk.map(processItem));
results.push(...chunkResults);
}
return results;
}
2. 异步迭代器 (for-await-of)
async function processStream() {
const stream = getReadableStream(); // 可读流或异步生成器
for await (const chunk of stream) {
await processChunk(chunk);
}
}
3. 异步生成器
async function* asyncGenerator(start, end) {
for (let i = start; i <= end; i++) {
// 模拟异步操作
await new Promise(resolve => setTimeout(resolve, 100));
yield i;
}
}
(async () => {
for await (const num of asyncGenerator(1, 5)) {
console.log(num); // 1, 2, 3, 4, 5 (每100ms输出一个)
}
})();
六、错误处理最佳实践
1. 分层错误处理
async function mainProcess() {
try {
// 核心业务逻辑
const input = await loadInput();
const processed = await processData(input);
await saveResults(processed);
} catch (error) {
// 业务级错误处理
if (error instanceof ValidationError) {
return { status: 'invalid', error };
}
if (error instanceof NetworkError) {
await retryOperation();
return { status: 'retrying' };
}
// 无法处理的错误重新抛出
throw error;
}
}
// 顶层错误处理
mainProcess()
.catch(error => {
console.error('未处理的错误:', error);
sendErrorReport(error);
});
2. 全局错误处理
// 浏览器环境
window.addEventListener('unhandledrejection', event => {
event.preventDefault();
console.error('未处理的Promise拒绝:', event.reason);
});
// Node.js环境
process.on('unhandledRejection', (reason, promise) => {
console.error('未处理的拒绝:', reason);
});
七、性能优化策略
1. 避免不必要的顺序等待
// 优化前:不必要的顺序执行
async function loadDashboard() {
const user = await fetchUser(); // 等待完成
const posts = await fetchPosts(); // 再开始
const stats = await fetchStats(); // 再开始
return { user, posts, stats };
}
// 优化后:并行执行
async function loadDashboard() {
// 同时启动所有请求
const [user, posts, stats] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchStats()
]);
return { user, posts, stats };
}
2. 缓存 Promise 结果
const userCache = new Map();
async function getUser(userId) {
if (!userCache.has(userId)) {
userCache.set(userId, fetchUser(userId));
}
return userCache.get(userId);
}
3. 避免阻塞事件循环
async function processLargeArray(array) {
const results = [];
const CHUNK_SIZE = 1000;
for (let i = 0; i < array.length; i += CHUNK_SIZE) {
const chunk = array.slice(i, i + CHUNK_SIZE);
// 允许事件循环处理其他任务
await new Promise(resolve => setTimeout(resolve, 0));
results.push(...processChunk(chunk));
}
return results;
}
八、现代特性
1. 顶层 await (ES2022)
// 在模块顶层直接使用 await
const config = await loadConfig();
export default config;
2. Promise.withResolvers (ES2024)
// 创建可外部控制的 Promise
const { promise, resolve, reject } = Promise.withResolvers();
// 在事件处理中解决
document.getElementById('btn').addEventListener('click', () => {
resolve('按钮被点击');
});
// 使用
const result = await promise;
console.log(result); // "按钮被点击"
九、常见陷阱与解决方案
| 陷阱 | 错误示例 | 解决方案 |
|---|---|---|
| 忘记 await | saveData(data); | await saveData(data); |
| 循环中的错误处理 | items.forEach(async item => { await process(item); }) | 使用 for...of 循环 |
| 不必要的顺序化 | await p1; await p2; | 使用 Promise.all([p1, p2]) |
| 资源未释放 | 缺少 finally 块 | 使用 try/finally 或 try/catch/finally |
| Promise 未处理拒绝 | fetch('/api'); | 添加错误处理逻辑 |
十、总结:async/await 最佳实践
- 优先使用 async/await:使异步代码更易读、更接近同步风格
- 合理处理错误:使用 try/catch 或 .catch() 处理潜在错误
- 优化并行执行:对独立操作使用 Promise.all
- 避免阻塞主线程:拆分 CPU 密集型任务
- 使用现代特性:利用顶层 await 和 Promise 组合方法
- 资源清理:始终在 finally 块中释放资源
- 全局错误处理:添加 unhandledrejection 事件监听器
// 最佳实践示例
async function robustAsyncOperation(params) {
try {
// 并行执行独立操作
const [resource, config] = await Promise.all([
acquireResource(),
loadConfig()
]);
// 顺序执行依赖操作
const data = await processData(resource, config);
// 返回结果
return { status: 'success', data };
} catch (error) {
// 错误分类处理
if (error instanceof NetworkError) {
return { status: 'retry', data: cachedData };
}
// 未知错误
console.error('操作失败:', error);
throw error;
} finally {
// 资源清理
releaseResources();
}
}
async/await 是现代 JavaScript 异步编程的终极解决方案,它:
- 使异步代码更易读、更易维护
- 提供同步风格的错误处理机制
- 兼容所有现代浏览器和 Node.js
- 与现有 Promise 代码无缝集成
- 支持复杂的控制流(循环、条件等)
掌握 async/await 将显著提升你的 JavaScript 异步编程能力,让你能够编写更简洁、更健壮的异步代码。
Promise,then 与 async,await 相互转换
Promise.then 与 async/await 相互转换指南
1. 基本转换原理
// Promise.then 风格
function getData() {
return fetchData()
.then(data => process(data))
.catch(err => handleError(err));
}
// async/await 风格
async function getData() {
try {
const data = await fetchData();
return process(data);
} catch (err) {
handleError(err);
}
}
2. 链式调用转换
// Promise.then 链
fetchUser(userId)
.then(user => fetchOrders(user.id))
.then(orders => getLatestOrder(orders))
.then(order => console.log(order))
.catch(console.error);
// async/await 等效
async function displayLatestOrder() {
try {
const user = await fetchUser(userId);
const orders = await fetchOrders(user.id);
const order = await getLatestOrder(orders);
console.log(order);
} catch (err) {
console.error(err);
}
}
3. 错误处理转换
// .catch 转换
promise
.then(handleSuccess)
.catch(handleError); // 捕获前面所有错误
// async/await 等效
try {
await promise;
handleSuccess();
} catch (err) {
handleError(err);
}
// .finally 转换
promise
.then(handleResult)
.finally(cleanup);
// async/await 等效
try {
const result = await promise;
handleResult(result);
} finally {
cleanup();
}
4. 并行执行转换
// Promise.all 转换
Promise.all([task1(), task2(), task3()])
.then(([r1, r2, r3]) => combineResults(r1, r2, r3));
// async/await 等效
async function parallelTasks() {
const [r1, r2, r3] = await Promise.all([task1(), task2(), task3()]);
return combineResults(r1, r2, r3);
}
// Promise.race 转换
Promise.race([fetchData(), timeout(5000)])
.then(firstResult => handleResult(firstResult));
// async/await 等效
async function withTimeout() {
const result = await Promise.race([fetchData(), timeout(5000)]);
handleResult(result);
}
5. 循环中的转换
// Promise 链中的循环
function processItems(items) {
let chain = Promise.resolve();
items.forEach(item => {
chain = chain.then(() => processItem(item));
});
return chain;
}
// async/await 等效
async function processItems(items) {
for (const item of items) {
await processItem(item);
}
}
// 并行循环
Promise.all(items.map(item => processItem(item)));
// async/await 等效
async function processAll(items) {
await Promise.all(items.map(item => processItem(item)));
}
6. 混合使用技巧
// 在 async 函数中使用 .then
async function fetchWithLogging() {
const data = await fetchData()
.then(res => {
console.log('数据已接收');
return res;
})
.catch(err => {
console.error('获取失败', err);
throw err;
});
return process(data);
}
// 在 .then 中使用 async 函数
fetchUser(userId)
.then(async user => {
const orders = await fetchOrders(user.id);
return { user, orders };
})
.then(console.log);
7. 高级模式转换
// 重试机制转换
// Promise 风格
function retry(fn, retries) {
return fn().catch(err =>
retries > 0
? retry(fn, retries - 1)
: Promise.reject(err)
);
}
// async/await 风格
async function retry(fn, retries) {
try {
return await fn();
} catch (err) {
if (retries <= 0) throw err;
return retry(fn, retries - 1);
}
}
// 取消机制
// Promise 风格
function cancellable(promise) {
let cancel;
const wrapped = new Promise((resolve, reject) => {
cancel = reject;
promise.then(resolve).catch(reject);
});
return [wrapped, () => cancel(new Error('操作取消'))];
}
// async/await 风格
async function cancellable(asyncFn) {
const controller = new AbortController();
const promise = asyncFn(controller.signal);
return {
result: promise,
cancel: () => controller.abort()
};
}
转换规则速查表
| Promise.then 模式 | async/await 等效 |
|---|---|
.then(response => {...}) | const response = await ... |
.catch(error => {...}) | try/catch 块 |
.finally(() => {...}) | try/catch 后的 finally 块 |
Promise.all([p1, p2]) | const [r1, r2] = await Promise.all([p1, p2]) |
Promise.race([p1, p2]) | const result = await Promise.race([p1, p2]) |
new Promise((resolve, reject) => {...}) | 直接使用 async 函数 |
.then().then().catch() 链 | 多个 await + try/catch |
返回 Promise.resolve(value) | 直接 return value |
返回 Promise.reject(error) | throw error |
最佳实践建议
-
优先使用 async/await:
// 更推荐 async function getUserData() { const user = await fetchUser(); return transform(user); } -
在需要精细控制时使用 .then:
// 特定场景更合适 function logFetch() { fetchData() .then(data => console.log('收到数据:', data)) .catch(err => console.error('错误:', err)); } -
混合使用时保持清晰:
// 良好的混合示例 async function processPipeline() { const rawData = await fetchData(); return transform(rawData) .then(result => { logResult(result); return result; }) .catch(err => { handleTransformError(err); throw err; }); } -
避免常见陷阱:
// 错误:忘记 await async function example() { const data = fetchData(); // 应该是 await fetchData() console.log(data); // 输出 Promise 对象 } // 错误:在循环中误用 await async function slowLoop() { for (const url of urls) { await fetch(url); // 顺序执行,效率低下 } } // 正确:并行执行 async function fastLoop() { await Promise.all(urls.map(url => fetch(url))); }
掌握这些转换技巧可以让你:
- 在旧代码库中逐步引入 async/await
- 根据场景选择最合适的异步风格
- 阅读和理解不同风格的异步代码
- 编写更清晰、更易维护的异步逻辑
then的第二个回调 与 catch回调
JavaScript Promise 错误处理详解:.then 的第二个回调 vs .catch
在 Promise 链式调用中,错误处理有两种主要方式:.then() 的第二个回调函数和 .catch() 方法。虽然它们都用于处理 rejected 状态,但在行为上有重要区别。
核心区别对比
| 特性 | .then 的第二个回调 | .catch 回调 |
|---|---|---|
| 作用范围 | 仅处理前一个 Promise 的 rejection | 捕获链中前面所有未处理的 rejection |
| 错误传递 | 处理当前错误后,后续链继续正常执行 | 处理错误后,后续链继续正常执行 |
| 位置要求 | 必须紧跟在前一个 Promise 后 | 可放在链的任何位置 |
| 链式影响 | 不影响后续错误处理 | 会拦截所有前面的未处理错误 |
| 推荐场景 | 特定步骤的错误恢复 | 全局错误处理 |
| 返回值影响 | 返回的值传递给下一个 .then() | 返回的值传递给下一个 .then() |
详细解析与示例
1. 作用范围差异
.then 的第二个回调只处理它直接连接的 Promise 的 rejection:
// 只处理 promiseA 的拒绝
promiseA
.then(
result => console.log("成功:", result),
error => console.log("处理错误A:", error) // 只处理 promiseA 的拒绝
)
.then(() => promiseB) // 如果 promiseB 被拒绝
.catch(error => console.log("捕获错误B:", error)); // 会捕获 promiseB 的拒绝
.catch 回调会捕获链中前面所有未处理的 rejection:
promiseA
.then(result => console.log("步骤1:", result))
.then(() => promiseB) // 如果 promiseB 被拒绝
.catch(error => console.log("捕获所有错误:", error)) // 捕获 promiseA 或 promiseB 的拒绝
.then(() => promiseC) // 如果 promiseC 被拒绝
.catch(error => console.log("捕获新错误:", error)); // 捕获 promiseC 的拒绝
2. 错误恢复流程
两种方式都能从错误中恢复,但作用域不同:
// 使用 .then 的第二个回调局部恢复
fetchUserData()
.then(
user => updateProfile(user.id),
error => {
console.log("用户数据获取失败,使用默认值");
return getDefaultUser(); // 返回恢复值
}
)
.then(user => console.log("处理用户:", user));
// 使用 .catch 全局恢复
processOrder()
.then(order => chargePayment(order))
.catch(error => {
console.log("支付失败:", error);
return retryPayment(); // 全局恢复
})
.then(() => sendConfirmation());
3. 组合使用模式
实际开发中经常组合使用两者:
fetchInitialData()
.then(validateData, handleValidationError) // 特定步骤错误处理
.then(processData)
.then(saveResults)
.catch(handleGlobalError); // 全局错误兜底
4. 错误冒泡行为
未被处理的 rejection 会一直向下传递:
Promise.reject(new Error("原始错误"))
.then(result => console.log("步骤1")) // 跳过
.then(
result => console.log("步骤2"),
error => console.log("处理原始错误:", error) // 捕获原始错误
)
.then(() => Promise.reject(new Error("新错误"))) // 新错误
.catch(error => console.log("处理新错误:", error)); // 捕获新错误
最佳实践建议
1. 优先使用 .catch 的场景
- 全局错误处理:在链的末尾添加兜底处理
- 简化代码:当不需要区分特定步骤错误时
- 错误日志:集中记录所有未处理的错误
// 推荐:简洁的全局错误处理
apiCall()
.then(processData)
.then(saveResult)
.catch(error => {
console.error("操作失败:", error);
notifyAdmin(error);
});
2. 使用 .then 第二个参数的场景
- 特定步骤恢复:某个操作有特殊的恢复逻辑
- 错误转换:将特定错误转换为更有意义的错误类型
- 条件处理:只处理特定类型的错误
// 特定步骤的错误恢复
fetchExternalAPI()
.then(
data => parseData(data),
error => {
if (error.status === 404) {
return fetchFallbackData(); // 特定恢复
}
throw error; // 其他错误继续传递
}
)
.catch(handleOtherErrors);
3. 高级错误处理模式
错误分类处理:
async function processTransaction() {
try {
const user = await getUser();
const payment = await chargePayment(user);
return await confirmTransaction(payment);
} catch (error) {
if (error instanceof NetworkError) {
return retryAfterDelay();
} else if (error instanceof PaymentError) {
return notifyPaymentFailure();
} else {
throw error;
}
}
}
错误边界模式:
function withErrorBoundary(promise, fallback) {
return promise
.catch(error => {
logError(error);
return fallback;
});
}
// 使用
withErrorBoundary(
fetchCriticalData(),
getCachedData()
).then(data => renderUI(data));
常见陷阱与解决方案
1. 错误处理遗漏
// 危险:可能遗漏错误
promise
.then(handler1)
.then(handler2); // 如果 handler2 抛出错误,没有处理
// 解决方案:添加全局 catch
promise
.then(handler1)
.then(handler2)
.catch(globalErrorHandler);
2. 过度嵌套
// 反模式:过度使用 .then 的第二个参数
promise
.then(success1, error1)
.then(success2, error2)
.then(success3, error3);
// 更好:使用 .catch 简化
promise
.then(success1)
.then(success2)
.then(success3)
.catch(error => {
// 统一处理所有错误
});
3. 错误吞噬
// 危险:可能吞噬错误
promise
.then(result => {
throw new Error("处理错误");
}, handleError) // 这个错误处理不会捕获上面抛出的错误
.catch(globalErrorHandler); // 这里才会捕获
// 明确:每个错误处理只负责特定范围
promise
.then(
result => process(result),
error => handleInitialError(error)
)
.then(nextStep)
.catch(handleAllErrors);
决策流程图
graph TD
A[需要处理错误] --> B{需要特定步骤处理?}
B -->|是| C[使用 .then 的第二个参数]
B -->|否| D[使用 .catch]
C --> E{需要全局兜底?}
D --> E
E -->|是| F[在链尾添加 .catch]
E -->|否| G[结束]
总结与黄金法则
- 优先使用
.catch:作为全局错误处理器,放在链的末尾 - 特定恢复使用
.then第二个参数:当某个步骤需要特殊错误处理 - 明确每个处理器的职责范围:
.then的第二个参数:只处理前一个 Promise 的拒绝.catch:处理前面所有未处理的拒绝
- 始终添加全局错误处理:防止未处理的 Promise 拒绝
- 在错误处理器中明确控制流程:
- 返回正常值:继续执行后续操作
- 抛出错误:将错误传递给下一个处理器
// 黄金实践示例
criticalOperation()
.then(
result => specificProcess(result),
error => handleSpecificError(error) // 特定错误恢复
)
.then(nextStep)
.then(finalStep)
.catch(error => { // 全局兜底
console.error("操作失败:", error);
emergencyRecovery();
})
.finally(cleanupResources);
理解 .then 的第二个回调与 .catch 的区别,能让你更精准地控制 Promise 链中的错误处理流程,构建更健壮的异步 JavaScript 应用。