JavaScript 异步编程:Promise 与 async/await 详解
·
一、Promise
1. 什么是 Promise?
Promise 是 JavaScript 中用于处理异步操作的对象,它代表一个异步操作的最终完成(或失败)及其结果值。
2. Promise 的三种状态
Pending(待定):初始状态,既不是成功,也不是失败状态
Fulfilled(已兑现):意味着操作成功完成
Rejected(已拒绝):意味着操作失败
3. 创建 Promise
const myPromise = new Promise((resolve, reject) => {
// 异步操作
if (/* 操作成功 */) {
resolve(value); // 将Promise状态改为fulfilled
} else {
reject(error); // 将Promise状态改为rejected
}
});
4. Promise 的方法
then()
myPromise.then(
(value) => { /* 成功时的处理 */ },
(error) => { /* 失败时的处理 */ }
);
catch()
myPromise.catch(
(error) => { /* 失败时的处理 */ }
);
finally()
myPromise.finally(
() => { /* 无论成功失败都会执行 */ }
);
5. Promise 的静态方法
Promise.resolve()
Promise.resolve(value); // 返回一个已解决的Promise
Promise.reject()
Promise.reject(reason); // 返回一个已拒绝的Promise
Promise.all()
Promise.all([promise1, promise2, ...])
.then((values) => { /* 所有Promise都成功 */ })
.catch((error) => { /* 任一Promise失败 */ });
Promise.race()
Promise.race([promise1, promise2, ...])
.then((value) => { /* 第一个完成的Promise */ });
Promise.allSettled() (ES2020)
Promise.allSettled([promise1, promise2, ...])
.then((results) => { /* 所有Promise都完成 */ });
6. Promise 链式调用
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => console.log(`Got the final result: ${finalResult}`))
.catch(failureCallback);
二、async/await
1. 什么是 async/await?
async/await 是建立在 Promise 之上的语法糖,让异步代码看起来更像同步代码。
2. async 函数
async function myFunction() {
return "Hello";
}
// 等同于
function myFunction() {
return Promise.resolve("Hello");
}
3. await 表达式
async function myFunction() {
const result = await somePromise;
console.log(result);
}
4. 错误处理
try/catch
async function myFunction() {
try {
const result = await somePromise;
console.log(result);
} catch (error) {
console.error(error);
}
}
结合 catch()
async function myFunction() {
const result = await somePromise.catch(error => {
console.error(error);
return defaultValue;
});
console.log(result);
}
5. 并行执行
async function myFunction() {
// 顺序执行
const result1 = await promise1;
const result2 = await promise2;
// 并行执行
const [result1, result2] = await Promise.all([promise1, promise2]);
}
6. async/await 与 Promise 的对比
// 使用Promise
function getJSON() {
return fetch('/data.json')
.then(response => {
if (response.status === 200) {
return response.json();
} else {
throw new Error('Unable to fetch data');
}
})
.then(data => console.log(data))
.catch(err => console.log(err));
}
// 使用async/await
async function getJSON() {
try {
const response = await fetch('/data.json');
if (response.status === 200) {
const data = await response.json();
console.log(data);
} else {
throw new Error('Unable to fetch data');
}
} catch (err) {
console.log(err);
}
}
三、最佳实践
1. Promise 最佳实践
总是返回或终止 Promise 链:在 then() 中总是返回新的 Promise 或 throw 错误
避免 Promise 嵌套:使用链式调用代替嵌套
使用 catch() 处理错误:不要忽略 Promise 的错误
命名 Promise 函数:提高代码可读性
2. async/await 最佳实践
尽量使用 try/catch:正确处理错误
避免不必要的 await:可以并行执行的不要顺序执行
考虑顶层 await:在模块顶层使用 await (ES2022)
不要混用 then() 和 async/await:选择一种风格并保持一致
四、高级用法
1. Promise 取消
function cancellablePromise(executor) {
let cancel;
const promise = new Promise((resolve, reject) => {
executor(resolve, reject);
cancel = reject;
});
promise.cancel = cancel;
return promise;
}
const p = cancellablePromise((resolve, reject) => {
setTimeout(() => resolve('Done'), 2000);
});
p.cancel('Operation cancelled');
2. async 生成器
async function* asyncGenerator() {
let i = 0;
while (i < 3) {
await new Promise(resolve => setTimeout(resolve, 1000));
yield i++;
}
}
(async function() {
for await (const num of asyncGenerator()) {
console.log(num);
}
})();
3. 顶层 await (ES2022)
// 在模块顶层直接使用await
const data = await fetch('/data.json').then(res => res.json());
console.log(data);
五、async 函数与 Promise 的关系
1. async 函数的返回值
不需要在 async 函数中显式返回 new Promise,因为 async 函数自动将返回值包装为 Promise:
async function foo() {
return 42; // 自动包装为 Promise.resolve(42)
}
// 等同于
function foo() {
return Promise.resolve(42);
}
2. 何时需要显式使用 new Promise
在以下情况下可能需要显式使用 new Promise:
包装回调式 API:如文件系统、网络操作、子进程、定时器、事件监听、Web API等
async function readFileAsync(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
}
需要手动控制 resolve/reject 时机:
async function waitForEvent(element, eventName) {
return new Promise((resolve) => {
element.addEventListener(eventName, resolve, { once: true });
});
}
六、async/await 与 Promise 的区别
| 特性 | Promise | async/await |
|---|---|---|
| 语法 | 链式调用 .then().catch() | 类似同步代码的语法 |
| 错误处理 | 使用 .catch() 或第二个参数 | 使用 try/catch 块 |
| 可读性 | 回调嵌套可能导致"回调地狱" | 线性执行,代码更清晰 |
| 调试 | 调试较困难(匿名函数) | 调试更直观(有函数名和行号) |
| 返回值 | 总是返回 Promise | 总是返回 Promise |
| 执行流程 | 基于事件循环的微任务 | 基于事件循环的微任务 |
七、如何配合使用 async/await 和 Promise
1. 最佳实践:混合使用场景
(1) 并行操作 + await
async function fetchAllData() {
// 并行启动所有请求
const [users, products, orders] = await Promise.all([
fetch('/api/users'),
fetch('/api/products'),
fetch('/api/orders')
]);
// 顺序处理结果
const userData = await users.json();
const productData = await products.json();
const orderData = await orders.json();
return { userData, productData, orderData };
}
(2) 需要精细控制 Promise 时
async function withTimeout(promise, ms) {
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
reject(new Error('Operation timed out'));
}, ms);
promise.then(
result => {
clearTimeout(timeoutId);
resolve(result);
},
error => {
clearTimeout(timeoutId);
reject(error);
}
);
});
}
// 使用
try {
const result = await withTimeout(fetch('/api/data'), 5000);
} catch (error) {
console.error('Error:', error);
}
2. 错误处理模式对比
Promise 风格
function getUser(id) {
return fetch(`/api/users/${id}`)
.then(response => {
if (!response.ok) throw new Error('Not found');
return response.json();
})
.catch(error => {
console.error('Fetch failed:', error);
throw error; // 继续传递错误
});
}
async/await 风格
async function getUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) throw new Error('Not found');
return await response.json();
} catch (error) {
console.error('Fetch failed:', error);
throw error; // 继续传递错误
}
}
3. 常见配合使用模式
(1) 异步初始化模式
let initialized = false;
let initializationPromise;
async function initialize() {
if (initialized) return;
if (!initializationPromise) {
initializationPromise = (async () => {
await loadConfig();
await connectToDB();
initialized = true;
})();
}
return initializationPromise;
}
// 使用
await initialize();
// 现在可以使用初始化后的资源
(2) 带缓存的异步请求
const cache = new Map();
async function getWithCache(url) {
if (cache.has(url)) {
return cache.get(url);
}
const promise = fetch(url)
.then(response => response.json())
.then(data => {
cache.set(url, data);
return data;
});
cache.set(url, promise);
return promise;
}
// 使用
const data = await getWithCache('/api/products');
八、性能考量
微任务开销:
await 会创建额外的微任务
在热代码路径中避免不必要的 await
内存使用:
每个 await 都会保留执行上下文
长异步链可能增加内存压力
优化技巧:
// 次优:创建两个微任务
async function foo() {
return await bar();
}
// 优化:只创建一个微任务
async function foo() {
return bar();
}
九、常见问题与解决方案
1. Promise 常见问题
问题:忘记返回 Promise
// 错误
somePromise.then(value => {
doSomething(value);
});
// 正确
somePromise.then(value => {
return doSomething(value);
});
问题:忽略错误处理
// 错误
somePromise.then(value => console.log(value));
// 正确
somePromise
.then(value => console.log(value))
.catch(error => console.error(error));
2. async/await 常见问题
问题:忘记 await
// 错误
async function foo() {
const result = someAsyncFunction(); // 忘记await
console.log(result); // 输出Promise对象
}
// 正确
async function foo() {
const result = await someAsyncFunction();
console.log(result);
}
问题:在循环中错误使用 await
// 低效 - 顺序执行
for (const url of urls) {
const response = await fetch(url);
console.log(await response.json());
}
// 高效 - 并行执行
const promises = urls.map(url => fetch(url).then(res => res.json()));
const results = await Promise.all(promises);
results.forEach(result => console.log(result));
为什么我的 async 函数返回 undefined?
async function foo() {
// 忘记 return
Promise.resolve(42);
}
const result = await foo(); // undefined
解决方案:确保返回你想要的值(Promise 会自动包装)
async function foo() {
return Promise.resolve(42); // 显式返回
// 或者直接 return 42;
}
什么时候应该使用 return await?
async function foo() {
try {
return await riskyOperation(); // 正确:可以捕获错误
} catch (error) {
return fallbackValue;
}
}
async function bar() {
return riskyOperation(); // 错误:无法捕获错误
}
规则:
在 try/catch 块内使用 return await 以捕获错误
否则直接 return Promise 更高效(少一次微任务)
如何取消 async 函数?
function createCancellableAsyncTask(task) {
let cancel;
const promise = new Promise((resolve, reject) => {
cancel = reject;
task().then(resolve, reject);
});
return { promise, cancel };
}
// 使用
const { promise, cancel } = createCancellableAsyncTask(async () => {
await longRunningOperation();
});
// 取消
cancel(new Error('User cancelled'));
十、总结
Promise 和 async/await 都是 JavaScript 中处理异步操作的重要工具:
Promise 提供了更结构化的异步编程方式,避免了回调地狱
async/await 让异步代码看起来更像同步代码,提高了可读性
两者可以结合使用,async 函数总是返回 Promise,await 后面可以跟任何 thenable 对象
在实际开发中,应根据场景选择合适的方式,并遵循最佳实践
JavaScript 异步编程:async/await 与 Promise 的关系与配合使用
async/await 是 Promise 的语法糖,底层仍然是 Promise
不需要在 async 函数中显式返回 new Promise,除非:
包装回调式 API
需要精细控制 resolve/reject 时机
配合使用原则:
使用 async/await 处理线性异步逻辑
使用 Promise 方法(如 Promise.all)处理并行操作
在需要精细控制或包装旧代码时使用 new Promise
错误处理:
try/catch 用于 async/await
.catch() 用于 Promise 链
性能优化:
避免不必要的 await
合理使用并行操作
更多推荐
所有评论(0)