第13章:异步JavaScript(Asynchronous JavaScript)
一、导读
1.1 本章学习目标
第13章介绍JavaScript的异步编程。本章核心目标:
- 理解同步和异步的区别
- 掌握回调函数和回调地狱
- 深入理解Promise:状态、值、链式调用
- 熟练使用async/await
- 理解事件循环:宏任务和微任务
1.2 为什么本章如此重要
JavaScript是单线程语言,异步编程是处理耗时操作的关键。Promise和async/await是现代JavaScript异步编程的标准方式。理解事件循环的工作原理,才能写出正确的异步代码。
1.3 核心问题引导
JavaScript是单线程的,它是如何处理并发的?
Promise的三种状态是什么?状态转换是不可逆的吗?
async/await相比Promise有什么优势?
微任务和宏任务的执行顺序是什么?
二、核心概念详解
13.1 回调函数
javascript
// 同步回调
[1, 2, 3].forEach(x => console.log(x));
// 异步回调
setTimeout(() => {
console.log("This runs after 1 second");
}, 1000);
// 回调地狱
getUser(userId, (user) => {
getOrders(user.id, (orders) => {
getOrderDetails(orders[0].id, (details) => {
getProductInfo(details.productId, (product) => {
console.log(product);
// 层层嵌套,难以维护
});
});
});
});13.2 Promise
javascript
// 创建Promise
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
let success = true;
if (success) {
resolve("Data loaded");
} else {
reject("Error occurred");
}
}, 1000);
});
}
// 使用Promise
fetchData()
.then(data => {
console.log(data);
return "processed";
})
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log("Done"));
// Promise组合
let p1 = Promise.resolve(1);
let p2 = Promise.resolve(2);
let p3 = Promise.resolve(3);
// Promise.all:所有都成功
Promise.all([p1, p2, p3]).then(values => {
console.log(values); // [1, 2, 3]
});
// Promise.race:最快的
Promise.race([p1, p2, p3]).then(value => {
console.log(value); // 1
});
// Promise.allSettled:等待所有完成
Promise.allSettled([
Promise.resolve(1),
Promise.reject("error"),
Promise.resolve(3)
]).then(results => {
console.log(results);
// [{status: "fulfilled", value: 1},
// {status: "rejected", reason: "error"},
// {status: "fulfilled", value: 3}]
});
// Promise.any:第一个成功的
Promise.any([
Promise.reject("error1"),
Promise.resolve(2),
Promise.resolve(3)
]).then(value => console.log(value)); // 213.3 async/await
javascript
// async函数总是返回Promise
async function getData() {
return "data"; // 等价于 Promise.resolve("data")
}
getData().then(console.log);
// await等待Promise完成
async function loadData() {
try {
let data = await fetchData();
console.log(data);
let moreData = await fetchData();
console.log(moreData);
} catch (error) {
console.error("Error:", error);
}
}
// 并行执行
async function loadAll() {
let [data1, data2, data3] = await Promise.all([
fetchData(),
fetchData(),
fetchData()
]);
console.log(data1, data2, data3);
}
// 顺序执行vs并行执行
async function sequential() {
const start = Date.now();
const a = await delay(1000); // 1秒
const b = await delay(1000); // 又1秒
console.log(`Sequential: ${Date.now() - start}ms`); // ~2000ms
}
async function parallel() {
const start = Date.now();
const [a, b] = await Promise.all([
delay(1000),
delay(1000)
]);
console.log(`Parallel: ${Date.now() - start}ms`); // ~1000ms
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}13.4 事件循环
javascript
// 执行顺序
console.log("1"); // 同步
setTimeout(() => {
console.log("2"); // 宏任务
}, 0);
Promise.resolve().then(() => {
console.log("3"); // 微任务
});
console.log("4"); // 同步
// 输出顺序:1, 4, 3, 2
// 微任务:Promise.then, process.nextTick, MutationObserver
// 宏任务:setTimeout, setInterval, I/O, UI rendering
// 微任务在当前宏任务结束后立即执行
// 宏任务在下一个事件循环周期执行
// 实际例子
function processQueue() {
const queue = [];
return {
add(task) {
queue.push(task);
// 使用微任务确保所有同步代码先执行
Promise.resolve().then(() => {
while (queue.length > 0) {
const task = queue.shift();
task();
}
});
}
};
}三、实战案例
3.1 带重试的异步请求
javascript
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.log(`Attempt ${attempt} failed: ${error.message}`);
if (attempt === maxRetries) throw error;
// 指数退避
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// 使用
async function loadUserData(userId) {
try {
const user = await fetchWithRetry(`/api/users/${userId}`);
console.log(user);
} catch (error) {
console.error("Failed to load user after all retries:", error);
}
}
// 带超时的Promise
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) => {
setTimeout(() => reject(new Error("Timeout")), ms);
});
return Promise.race([promise, timeout]);
}
// 使用
async function loadData() {
try {
const data = await withTimeout(fetchData(), 5000);
console.log(data);
} catch (error) {
console.error(error.message);
}
}四、本章小结
第13章介绍了异步JavaScript:回调函数、Promise、async/await、事件循环。Promise和async/await是现代异步编程的标准方式。事件循环决定了代码的执行顺序。
五、思考与练习
思考题
Promise的链式调用是如何工作的?
async/await的错误处理有什么注意事项?
如何取消一个正在执行的Promise?
Web Workers和事件循环有什么关系?
编程练习
实现一个带并发限制的异步任务调度器。
实现一个Promise.all的polyfill。
使用async/await重构回调地狱代码。