知识篇 -- JS异步编程:从回调到Async/Await
Ray Shine
2024/1/23 JavaScript进阶知识异步
本文为博主原创文章,遵循
CC 4.0 BY-SA
版权协议,转载请附上原文出处链接和本声明。
如有侵权,请联系
本博主
删除。
JavaScript作为一门单线程语言,在处理耗时操作(如网络请求、文件读写、定时器)时,如果采用同步方式,会导致主线程阻塞,页面卡死。为了解决这个问题,JavaScript引入了异步编程的概念。异步编程允许程序在等待耗时操作完成的同时,继续执行其他任务,从而保持页面的响应性。本文将深入探讨JavaScript异步编程的演进,从传统的回调函数到现代的Promise和Async/Await。
# 一、回调函数 (Callbacks):异步编程的起点 起点
回调函数是JavaScript异步编程最基本的形式。它是一个函数,作为参数传递给另一个函数,并在后者完成其操作后被调用。
- 优点:简单直观,易于理解。
- 缺点:
- 回调地狱 (Callback Hell):当有多个相互依赖的异步操作时,代码会层层嵌套,导致可读性差、难以维护。
- 错误处理困难:错误通常需要通过参数传递,且难以统一处理。
- 控制反转 (Inversion of Control):将控制权交给了回调函数,使得代码的执行顺序变得不那么直观。
示例:
function fetchData(callback) {
setTimeout(() => {
const data = "Some data";
callback(data);
}, 1000);
}
function processData(data, callback) {
setTimeout(() => {
const processed = data.toUpperCase();
callback(processed);
}, 500);
}
fetchData(function(data) {
console.log("Fetched:", data); // Fetched: Some data
processData(data, function(processed) {
console.log("Processed:", processed); // Processed: SOME DATA
});
});
回调地狱示例:
// 假设有三个异步操作,依次执行
asyncOperation1(function(result1) {
asyncOperation2(result1, function(result2) {
asyncOperation3(result2, function(result3) {
console.log("最终结果:", result3);
});
});
});
# 二、Promise:解决回调地狱的方案 (ES6新增) ES6
Promise是ES6引入的一种处理异步操作的机制,它代表了一个异步操作的最终完成(或失败)及其结果值。Promise的出现极大地改善了回调地狱问题,使得异步代码更具可读性和可维护性。
- 状态:
pending(进行中):初始状态,既不是成功也不是失败。fulfilled(已成功):操作成功完成。rejected(已失败):操作失败。
- 特点:
- 链式调用:通过
.then()方法可以链式地处理异步操作的结果。 - 统一错误处理:通过
.catch()方法可以集中处理链中任何环节的错误。 - 控制权回归:将控制权从回调函数中夺回,使得异步操作的流程更加清晰。
- 链式调用:通过
示例:
function fetchDataPromise() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve("Some data");
} else {
reject("Error fetching data");
}
}, 1000);
});
}
function processDataPromise(data) {
return new Promise(resolve => {
setTimeout(() => {
resolve(data.toUpperCase());
}, 500);
});
}
fetchDataPromise()
.then(data => {
console.log("Fetched:", data);
return processDataPromise(data); // 返回一个新的Promise,实现链式调用
})
.then(processed => {
console.log("Processed:", processed);
})
.catch(error => {
console.error("Error:", error); // 统一处理错误
})
.finally(() => {
console.log("Promise chain finished.");
});
Promise API:
Promise.all(iterable): 等待所有Promise都成功,或其中一个失败。Promise.race(iterable): 返回第一个解决或拒绝的Promise的结果。Promise.any(iterable)(ES2021新增): 返回第一个成功的Promise的结果,所有都失败则拒绝。Promise.allSettled(iterable)(ES2020新增): 等待所有Promise都完成(无论成功或失败)。
# 三、Async/Await:更优雅的异步编程 (ES2017新增) ES2017
async 和 await 是基于Promise的语法糖,它们使得异步代码的编写和阅读体验更接近同步代码,进一步提高了可读性和可维护性。
async函数:async关键字用于声明一个函数是异步的。async函数总是返回一个Promise。如果函数内部返回一个非Promise值,它会被自动包装成一个已解决的Promise。- 可以在
async函数内部使用await关键字。
await表达式:await关键字只能在async函数内部使用。await会暂停async函数的执行,直到其等待的Promise解决(fulfilled)或拒绝(rejected)。- 如果Promise解决,
await表达式会返回解决的值。 - 如果Promise拒绝,
await表达式会抛出错误,需要使用try...catch来捕获。
示例:
async function performAsyncOperations() {
try {
console.log("Starting async operations...");
const data = await fetchDataPromise(); // 等待 fetchDataPromise 完成
console.log("Fetched data:", data);
const processed = await processDataPromise(data); // 等待 processDataPromise 完成
console.log("Processed data:", processed);
console.log("All operations finished successfully.");
} catch (error) {
console.error("An error occurred:", error); // 集中处理错误
} finally {
console.log("Async function finished.");
}
}
performAsyncOperations();
优点:
- 代码可读性高:异步代码看起来像同步代码,逻辑流程更清晰。
- 错误处理简单:可以使用传统的
try...catch语句来处理异步操作中的错误。 - 调试友好:更容易在调试器中逐步执行异步代码。
# 四、异步编程的未来:Generator函数、Observable等 未来
除了回调、Promise和Async/Await,JavaScript异步编程还在不断发展:
- Generator函数:通过
function*和yield关键字,可以创建可暂停和恢复的函数,是实现协程的基础,也是Promise和Async/Await的底层原理之一。 - Observable (RxJS):一种处理事件流和异步数据流的强大工具,适用于复杂的响应式编程场景。
# 五、总结
JavaScript异步编程是现代Web开发不可或缺的一部分。从最初的回调函数到Promise,再到如今广泛使用的Async/Await,JavaScript在不断演进,为开发者提供了越来越优雅和强大的工具来处理异步操作。掌握这些异步编程范式,能够让你编写出更具响应性、更易维护的Web应用程序。