JavaScript异步编程完全指南:从回调到async/await

3 min read
JavaScript异步编程完全指南:从回调到async/await
JavaScript异步编程是现代Web开发的核心技能之一。从最初的回调函数,到Promise,再到async/await,异步编程的写法越来越优雅和易读。本文将带你深入理解JavaScript异步编程的发展历程。
什么是异步编程?
异步编程是一种编程范式,允许程序在等待某些操作完成时继续执行其他代码,而不是阻塞整个程序的执行。
同步 vs 异步
// 同步代码示例
console.log('开始');
console.log('执行中');
console.log('结束');
// 异步代码示例
console.log('开始');
setTimeout(() => {
console.log('异步操作完成');
}, 1000);
console.log('结束');
回调函数时代
在ES6之前,JavaScript主要使用回调函数来处理异步操作:
function fetchData(callback) {
setTimeout(() => {
const data = { id: 1, name: 'JavaScript' };
callback(null, data);
}, 1000);
}
fetchData((error, data) => {
if (error) {
console.error('发生错误:', error);
} else {
console.log('获取数据:', data);
}
});
回调地狱
当需要进行多个异步操作时,回调函数会导致代码嵌套过深:
fetchUser(id, (err, user) => {
if (err) {
console.error(err);
} else {
fetchPosts(user.id, (err, posts) => {
if (err) {
console.error(err);
} else {
fetchComments(posts[0].id, (err, comments) => {
if (err) {
console.error(err);
} else {
console.log(comments);
}
});
}
});
}
});
Promise的革命
ES6引入了Promise,极大地改善了异步代码的可读性:
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = { id: 1, name: 'JavaScript' };
resolve(data);
}, 1000);
});
}
fetchData()
.then(data => {
console.log('获取数据:', data);
return fetchMoreData(data.id);
})
.then(moreData => {
console.log('更多数据:', moreData);
})
.catch(error => {
console.error('发生错误:', error);
});
Promise的三种状态
- Pending(进行中): 初始状态,既不是成功,也不是失败状态
- Fulfilled(已成功): 操作成功完成
- Rejected(已失败): 操作失败
async/await:异步编程的未来
ES2017引入的async/await让异步代码看起来像同步代码:
async function fetchAllData() {
try {
const user = await fetchUser(id);
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts[0].id);
console.log({ user, posts, comments });
} catch (error) {
console.error('发生错误:', error);
}
}
fetchAllData();
并发处理
使用Promise.all()
处理并发异步操作:
async function fetchConcurrentData() {
try {
const [users, posts, comments] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchComments()
]);
console.log({ users, posts, comments });
} catch (error) {
console.error('发生错误:', error);
}
}
实际应用场景
1. API请求
async function getWeather(city) {
try {
const response = await fetch(`/api/weather?city=${city}`);
const data = await response.json();
return data;
} catch (error) {
throw new Error(`获取天气信息失败: ${error.message}`);
}
}
2. 文件读取
const fs = require('fs').promises;
async function readConfig() {
try {
const data = await fs.readFile('config.json', 'utf8');
return JSON.parse(data);
} catch (error) {
console.error('读取配置文件失败:', error);
return {};
}
}
最佳实践
1. 错误处理
async function robustAsyncFunction() {
try {
const result = await riskyOperation();
return result;
} catch (error) {
console.error('操作失败:', error);
// 根据错误类型进行不同处理
if (error.code === 'NETWORK_ERROR') {
return retryOperation();
}
throw error;
}
}
2. 避免忘记await
// 错误示例
async function badExample() {
const data = fetchData(); // 忘记await,data是Promise对象
console.log(data.name); // undefined
}
// 正确示例
async function goodExample() {
const data = await fetchData(); // 正确使用await
console.log(data.name); // JavaScript
}
3. 合理使用Promise.all
// 顺序执行(较慢)
async function sequential() {
const result1 = await operation1();
const result2 = await operation2();
const result3 = await operation3();
return [result1, result2, result3];
}
// 并发执行(更快)
async function concurrent() {
const [result1, result2, result3] = await Promise.all([
operation1(),
operation2(),
operation3()
]);
return [result1, result2, result3];
}
总结
JavaScript异步编程经历了从回调函数到Promise,再到async/await的发展过程。每一次进化都让异步代码变得更加优雅和易于维护:
- 回调函数: 简单直接,但容易产生回调地狱
- Promise: 解决了回调地狱问题,支持链式调用
- async/await: 让异步代码看起来像同步代码,大大提升了可读性
在现代JavaScript开发中,建议优先使用async/await,在需要并发处理时结合Promise.all使用。
掌握异步编程是成为优秀JavaScript开发者的必经之路。希望这篇文章能帮助你更好地理解和应用JavaScript异步编程技术。
这篇文章是JavaScript进阶系列的第一篇,后续还会深入探讨闭包、原型链、模块化等主题。
1
Subscribe to my newsletter
Read articles from Hi Context Space directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
