JavaScript 异步编程:从“等待”到“流畅”的艺术
·
想象一下:你同时在餐厅点餐、给朋友发信息、听音乐。如果只能做完一件再做下一件,你会疯掉吗?这就是为什么我们需要异步编程!
什么是异步编程?
同步编程(传统方式):
console.log("开始做早餐");
// 等待10秒做煎蛋
console.log("煎蛋做好了");
console.log("开始煮咖啡");
// 等待5秒煮咖啡
console.log("咖啡煮好了");
// 总共需要15秒
异步编程(现代方式):
console.log("开始做早餐");
开始煎蛋(10秒后完成); // 不等待,继续下一步
开始煮咖啡(5秒后完成); // 立即开始煮咖啡
// 总共只需要10秒(两者同时进行)
简单来说:异步编程让你的程序可以同时处理多个任务,而不是傻傻等待!
为什么需要异步编程?
- 避免阻塞:网络请求可能需要几秒钟,不能让用户界面卡住
- 提高效率:同时处理多个操作
- 更好的用户体验:应用保持响应
异步编程的进化史
阶段1:回调函数(Callback Hell)
最早的异步处理方式:
// 获取用户数据后,再获取其订单,再获取订单详情...
getUserData(function(user) {
getUserOrders(user.id, function(orders) {
getOrderDetails(orders[0].id, function(details) {
getProductInfo(details.productId, function(product) {
console.log("最终产品信息:", product);
// 更多的嵌套...
});
});
});
});
问题:回调地狱!代码像金字塔一样,难以阅读和维护。
阶段2:Promise - 救世主登场
Promise:承诺更好的未来
Promise 是一个对象,表示一个异步操作的**最终完成(或失败)**及其结果值。
// Promise 的三种状态:
// 1. Pending(进行中) - 初始状态
// 2. Fulfilled(已成功) - 操作成功完成
// 3. Rejected(已失败) - 操作失败
创建 Promise
// 创建一个简单的 Promise
const myPromise = new Promise((resolve, reject) => {
// 异步操作,比如网络请求、读取文件等
setTimeout(() => {
const success = Math.random() > 0.3; // 70% 成功概率
if (success) {
resolve("操作成功!数据是: {name: '小明', age: 20}");
} else {
reject(new Error("操作失败:网络连接问题"));
}
}, 2000);
});
使用 Promise
// 使用 .then() 处理成功,.catch() 处理失败
myPromise
.then(result => {
console.log("成功:", result);
})
.catch(error => {
console.error("失败:", error.message);
})
.finally(() => {
console.log("无论成功失败,都会执行这里");
});
Promise 链式调用 - 解决回调地狱
// 模拟三个依次依赖的异步操作
function step1() {
return new Promise(resolve => {
setTimeout(() => resolve("第一步完成"), 1000);
});
}
function step2(data) {
return new Promise(resolve => {
setTimeout(() => resolve(`${data} → 第二步完成`), 1000);
});
}
function step3(data) {
return new Promise(resolve => {
setTimeout(() => resolve(`${data} → 第三步完成`), 1000);
});
}
// 链式调用,不再是嵌套!
step1()
.then(result => {
console.log(result);
return step2(result);
})
.then(result => {
console.log(result);
return step3(result);
})
.then(finalResult => {
console.log("最终结果:", finalResult);
})
.catch(error => {
console.error("出错:", error);
});
Promise 的实用方法
// 1. Promise.all - 等待所有Promise完成
const promise1 = fetchUser();
const promise2 = fetchProducts();
const promise3 = fetchSettings();
Promise.all([promise1, promise2, promise3])
.then(([user, products, settings]) => {
console.log("所有数据加载完成");
renderPage(user, products, settings);
})
.catch(error => {
console.error("至少一个请求失败:", error);
});
// 2. Promise.race - 竞速,第一个完成的获胜
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error("请求超时")), 5000);
});
Promise.race([fetchUserData(), timeoutPromise])
.then(data => {
console.log("在5秒内获取到数据");
})
.catch(error => {
console.error(error.message);
});
// 3. Promise.allSettled - 等待所有Promise完成(无论成功失败)
Promise.allSettled([promise1, promise2, promise3])
.then(results => {
results.forEach((result, index) => {
if (result.status === "fulfilled") {
console.log(`Promise ${index} 成功:`, result.value);
} else {
console.log(`Promise ${index} 失败:`, result.reason);
}
});
});
阶段3:async/await - 异步编程的终极形态
async/await 是 Promise 的语法糖,让你可以用同步的方式写异步代码!
基本用法
// async 函数总是返回一个 Promise
async function getUserData() {
// 假装这是从服务器获取数据
return { name: "小明", age: 20 };
}
// await 只能在 async 函数内部使用
async function main() {
console.log("开始获取用户数据...");
try {
const user = await getUserData(); // 等待Promise完成
console.log("获取到的用户:", user);
const orders = await getUserOrders(user.id); // 等待下一个Promise
console.log("用户的订单:", orders);
return { user, orders }; // 自动包装为Promise
} catch (error) {
console.error("获取数据失败:", error);
throw error; // 重新抛出错误
}
}
// 使用 async 函数
main().then(result => {
console.log("所有操作完成:", result);
});
对比:Promise vs async/await
Promise 写法:
function fetchData() {
return fetchUser()
.then(user => {
return fetchUserPosts(user.id)
.then(posts => {
return fetchPostComments(posts[0].id)
.then(comments => {
return { user, posts, comments };
});
});
})
.catch(error => {
console.error("出错:", error);
});
}
async/await 写法:
async function fetchData() {
try {
const user = await fetchUser();
const posts = await fetchUserPosts(user.id);
const comments = await fetchPostComments(posts[0].id);
return { user, posts, comments };
} catch (error) {
console.error("出错:", error);
throw error;
}
}
是不是清晰多了?就像写同步代码一样!
实际应用示例
示例1:用户登录流程
async function loginUser(email, password) {
// 显示加载中
showLoadingSpinner();
try {
// 1. 验证输入
if (!email || !password) {
throw new Error("邮箱和密码不能为空");
}
// 2. 发送登录请求(异步)
const user = await api.login(email, password);
// 3. 获取用户详情(依赖登录结果)
const userDetails = await api.getUserDetails(user.id);
// 4. 获取用户偏好设置(与上一步并行)
const [notifications, settings] = await Promise.all([
api.getNotifications(user.id),
api.getUserSettings(user.id)
]);
// 5. 更新UI
updateUserInterface(userDetails, notifications, settings);
console.log("登录成功!");
return userDetails;
} catch (error) {
// 统一错误处理
if (error.response?.status === 401) {
showErrorMessage("邮箱或密码错误");
} else if (error.response?.status === 429) {
showErrorMessage("尝试次数过多,请稍后再试");
} else {
showErrorMessage("登录失败,请检查网络连接");
}
throw error;
} finally {
// 无论成功失败,都隐藏加载动画
hideLoadingSpinner();
}
}
// 使用
loginButton.addEventListener("click", async () => {
try {
await loginUser(emailInput.value, passwordInput.value);
navigateToDashboard();
} catch (error) {
// 已经在上面的函数中处理了
}
});
示例2:图片批量下载器
async function downloadImages(imageUrls, maxConcurrent = 3) {
const results = [];
const errors = [];
// 分组下载,避免同时发起太多请求
for (let i = 0; i < imageUrls.length; i += maxConcurrent) {
const batch = imageUrls.slice(i, i + maxConcurrent);
const batchPromises = batch.map(async (url, index) => {
try {
console.log(`开始下载: ${url}`);
const imageData = await downloadImage(url);
console.log(`下载完成: ${url}`);
return { url, data: imageData, success: true };
} catch (error) {
console.error(`下载失败: ${url}`, error);
return { url, error: error.message, success: false };
}
});
const batchResults = await Promise.allSettled(batchPromises);
batchResults.forEach(result => {
if (result.status === "fulfilled") {
if (result.value.success) {
results.push(result.value);
} else {
errors.push(result.value);
}
}
});
// 批次之间稍作等待
await delay(500);
}
return { results, errors };
}
// 使用
const imageUrls = [
"https://example.com/image1.jpg",
"https://example.com/image2.jpg",
// ... 更多图片
];
downloadImages(imageUrls, 5).then(({ results, errors }) => {
console.log(`成功下载 ${results.length} 张图片`);
console.log(`${errors.length} 张图片下载失败`);
// 显示下载结果
renderImageGallery(results);
});
高级技巧与最佳实践
1. 不要滥用 async/await
不好的写法:
async function processItems(items) {
// 顺序执行,很慢!
for (const item of items) {
await processItem(item); // 等待每个完成
}
}
好的写法:
async function processItems(items) {
// 并行执行,很快!
const promises = items.map(item => processItem(item));
const results = await Promise.all(promises);
return results;
}
2. 错误处理要到位
async function robustFetch() {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP错误: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
// 细分错误类型
if (error.name === 'TypeError') {
console.error("网络错误或CORS问题");
// 尝试备用方案
return await fetchFromBackup(url);
} else if (error.name === 'SyntaxError') {
console.error("JSON解析错误");
throw new Error("服务器返回了无效的数据格式");
} else {
// 其他错误
console.error("未知错误:", error);
throw error;
}
}
}
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;
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error(`请求超时 (${timeout}ms)`);
}
throw error;
}
}
实战:创建一个简单的异步任务队列
class AsyncQueue {
constructor() {
this.queue = [];
this.processing = false;
}
// 添加任务到队列
enqueue(task) {
return new Promise((resolve, reject) => {
this.queue.push({ task, resolve, reject });
if (!this.processing) {
this.processQueue();
}
});
}
// 处理队列中的任务
async processQueue() {
this.processing = true;
while (this.queue.length > 0) {
const { task, resolve, reject } = this.queue.shift();
try {
const result = await task();
resolve(result);
} catch (error) {
reject(error);
}
// 可选:在任务之间添加延迟
await this.delay(100);
}
this.processing = false;
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// 使用示例
const taskQueue = new AsyncQueue();
// 添加多个任务(它们会按顺序执行)
taskQueue.enqueue(() => downloadFile("file1.pdf"));
taskQueue.enqueue(() => processImage("photo.jpg"));
taskQueue.enqueue(() => sendAnalyticsData());
console.log("所有任务已添加到队列,将按顺序执行");
学习路径建议
- 先理解 Promise 基础:then/catch/finally
- 掌握 async/await 语法:像写同步代码一样写异步
- 学习错误处理:try/catch 和 Promise.catch
- 掌握并行执行:Promise.all/race/allSettled
- 了解高级模式:队列、重试、超时等
总结
异步编程是现代 JavaScript 的核心技能:
- ✅ Promise:提供了标准的异步处理方式
- ✅ async/await:让异步代码清晰易读
- ✅ 错误处理:确保应用的健壮性
- ✅ 并行执行:充分利用计算资源
记住:异步不是关于让代码跑得更快,而是关于不让代码在等待时浪费时间。
开始在你的项目中使用 async/await 吧,你会发现异步编程也可以如此优雅! 🚀
提示:浏览器开发者工具的 Sources 面板中,可以逐步调试 async/await 代码,就像调试同步代码一样简单!
更多推荐


所有评论(0)