一、同步与异步的本质区别
同步和异步是编程中两种不同的执行模式,理解它们的区别是掌握异步编程的基础。
同步(Synchronous):代码按照书写顺序依次执行,前一个操作完成才能进行下一个操作。例如:
javascript
console.log('第一行');
console.log('第二行');
// 输出顺序永远是"第一行"然后"第二行"
异步(Asynchronous):代码的执行不按照书写顺序,某些操作会被放入任务队列,等待主线程空闲时执行。例如:
javascript
console.log('开始');
setTimeout(() => console.log('定时器回调'), 0);
console.log('结束');
// 输出顺序: 开始 -> 结束 -> 定时器回调
异步操作常见于:
- 网络请求(AJAX/fetch)
- 定时器(setTimeout/setInterval)
- 文件读写(Node.js)
- 数据库操作
关键区别:同步会阻塞代码执行,异步不会阻塞后续代码的执行。
二、回调地狱与Promise的诞生
回调地狱问题
在Promise出现之前,异步操作主要依赖回调函数,多层嵌套会导致"回调地狱":
javascript
getData(function(a) {
getMoreData(a, function(b) {
getMoreData(b, function(c) {
// 更多嵌套...
});
});
});
这种代码难以阅读、调试和维护,错误处理也非常复杂。
Promise的解决方案
Promise是ES6引入的异步编程解决方案,它通过链式调用解决了回调地狱问题。
Promise三大状态:
- Pending(进行中)
- Fulfilled(已成功)
- Rejected(已失败)
状态一旦改变就不可逆(Pending → Fulfilled 或 Pending → Rejected。
基本用法:
javascript
const promise = new Promise((resolve, reject) => {
// 异步操作
if (/* 成功 */) {
resolve(value); // 状态变为Fulfilled
} else {
reject(error); // 状态变为Rejected
}
});
promise.then(
value => { /* 成功处理 */ },
error => { /* 失败处理 */ }
);
链式调用示例:
javascript
function p1() {
return new Promise(resolve => {
setTimeout(() => resolve('结果1'), 1000);
});
}
function p2(data) {
return new Promise(resolve => {
setTimeout(() => resolve(data + ' 结果2'), 1000);
});
}
p1()
.then(p2)
.then(result => console.log(result))
.catch(error => console.error(error));
// 2秒后输出: "结果1 结果2"
Promise的链式调用使异步代码有了同步代码的阅读体验。
三、async/await:Promise的语法糖
async/await是ES2017引入的语法,基于Promise实现,但代码更加简洁。
async函数
- async函数总是返回一个Promise对象
- 如果返回值不是Promise,会自动包装成Promise
javascript
async function foo() {
return 'hello';
}
// 等价于
function foo() {
return Promise.resolve('hello');
}
await表达式
- await后面通常是一个Promise对象
- 会"暂停"async函数的执行,等待Promise解决
- 如果是rejected状态,会抛出异常
javascript
async function getData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('获取数据失败:', error);
}
}
async/await与Promise的关系
- async函数返回的是Promise对象
- await后面可以接任何thenable对象(有then方法的对象)
- async/await底层是基于Promise和生成器实现的语法糖
对比示例:
Promise版本:
javascript
function loadData() {
return fetchData()
.then(data => processData(data))
.then(result => displayResult(result))
.catch(error => handleError(error));
}
async/await版本:
javascript
async function loadData() {
try {
const data = await fetchData();
const result = await processData(data);
displayResult(result);
} catch (error) {
handleError(error);
}
}
async/await让异步代码看起来更像同步代码,提高了可读性。
四、错误处理机制对比
Promise的错误处理
- 使用.catch()捕获错误:
javascript
somePromise()
.then(handleSuccess)
.catch(handleError);
- 或者在then中传入第二个参数:
javascript
somePromise().then(
handleSuccess,
handleError
);
async/await的错误处理
使用try/catch结构:
javascript
async function foo() {
try {
const result = await somePromise();
// 处理结果
} catch (error) {
// 处理错误
}
}
五、实际应用场景
适合Promise的场景
- 需要并行执行多个异步操作时:
javascript
Promise.all([asyncTask1(), asyncTask2(), asyncTask3()])
.then(([result1, result2, result3]) => {
// 所有任务都完成后的处理
});
- 需要竞速执行多个异步操作时:
javascript
Promise.race([fetch1, fetch2])
.then(firstResult => {
// 使用最先返回的结果
});
适合async/await的场景
- 有前后依赖关系的异步操作:
javascript
async function processUserData(userId) {
const user = await getUser(userId);
const orders = await getOrders(user.id);
const payments = await getPayments(user.id);
return { user, orders, payments };
}
- 需要更清晰错误处理的复杂异步流程。
六、性能考量
- Promise创建后会立即执行,无法取消
- async/await不会带来额外性能开销,它只是语法糖
- 过度使用await会导致不必要的等待:
javascript
// 不推荐 - 顺序执行
const a = await getA();
const b = await getB();
// 推荐 - 并行执行
const [a, b] = await Promise.all([getA(), getB()]);
七、总结与最佳实践
- 演进历程:回调 → Promise → async/await
- 关系 :
- Promise是async/await的基础
- async函数返回Promise
- await等待Promise解决
- 最佳实践 :
- 简单异步操作可直接使用Promise
- 复杂异步流程推荐async/await
- 并行无依赖任务使用Promise.all
- 始终处理错误(.catch或try/catch)
- 注意事项 :
- 避免在循环中错误使用await
- 合理使用Promise缓存避免重复请求
- 在Node.js中注意Promise的内存泄漏问题
通过合理结合Promise和async/await,可以编写出既高效又易于维护的异步JavaScript代码。