跳到主要内容

JavaScript 异步编程

异步编程是 JavaScript 的核心特性,用于处理耗时操作而不阻塞主线程。

异步基础

同步与异步

// 同步代码 - 按顺序执行
console.log("第一步");
console.log("第二步");
console.log("第三步");

// 异步代码 - 不会等待执行完成
console.log("第一步");
setTimeout(() => {
console.log("异步任务完成");
}, 1000);
console.log("第三步"); // 先执行

回调函数

回调是最基本的异步处理方式:

function fetchData(callback) {
setTimeout(() => {
const data = { name: "张三", age: 20 };
callback(data);
}, 1000);
}

fetchData((result) => {
console.log("获取到的数据:", result);
});

回调地狱

回调嵌套过多会导致代码难以维护:

// 回调地狱示例
getUser(userId, (user) => {
getOrders(user.id, (orders) => {
getProducts(orders, (products) => {
getPrices(products, (prices) => {
// 嵌套太深,难以阅读
console.log(prices);
});
});
});
});

Promise

Promise 是解决回调地狱的方案,提供了更清晰的异步处理方式。

Promise 基本用法

const promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const success = true;
if (success) {
resolve("操作成功"); // 成功时调用
} else {
reject("操作失败"); // 失败时调用
}
}, 1000);
});

promise
.then(result => console.log(result)) // 成功处理
.catch(error => console.error(error)) // 错误处理
.finally(() => console.log("操作完成")); // 最终处理

Promise 状态

Promise 有三种状态:

  • pending(待定):初始状态
  • fulfilled(已兑现):操作成功
  • rejected(已拒绝):操作失败
const promise = new Promise((resolve, reject) => {
console.log("Promise 创建了");
// pending 状态
});

console.log("同步代码");

创建 Promise

// 包装异步操作
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

// 包装旧式回调函数
function readFilePromise(path) {
return new Promise((resolve, reject) => {
readFile(path, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}

Promise 方法

then

const promise = Promise.resolve(1);

promise.then(x => x + 1)
.then(x => x * 2)
.then(x => console.log(x)); // 4

catch

Promise.reject("错误")
.then(() => console.log("不会执行"))
.catch(err => {
console.error("捕获错误:", err);
return "恢复值";
})
.then(result => console.log(result)); // 恢复值

finally

fetch("/api/data")
.then(data => console.log(data))
.catch(err => console.error(err))
.finally(() => {
hideLoadingSpinner();
});

Promise 组合

Promise.all

等待所有 Promise 完成(全部成功才成功):

const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);

Promise.all([promise1, promise2, promise3])
.then(results => console.log(results)); // [1, 2, 3]

// 任意一个失败则整体失败
const failed = Promise.reject("失败");
Promise.all([promise1, failed, promise3])
.catch(err => console.error(err)); // 失败

Promise.allSettled

等待所有 Promise 完成(无论成功或失败):

const promises = [
Promise.resolve(1),
Promise.reject("失败"),
Promise.resolve(3)
];

Promise.allSettled(promises).then(results => {
results.forEach((result, index) => {
if (result.status === "fulfilled") {
console.log(`${index}: ${result.value}`);
} else {
console.log(`${index}: ${result.reason}`);
}
});
});

Promise.race

返回最先完成(无论成功或失败)的 Promise:

const slow = new Promise(resolve => setTimeout(() => resolve("慢"), 1000));
const fast = new Promise(resolve => setTimeout(() => resolve("快"), 500));

Promise.race([slow, fast])
.then(result => console.log(result)); // 快

Promise.any

返回最先成功的 Promise:

const fail = new Promise((resolve, reject) => 
setTimeout(() => reject("失败"), 1000));
const success = new Promise(resolve =>
setTimeout(() => resolve("成功"), 500));

Promise.any([fail, success])
.then(result => console.log(result)); // 成功

Promise 链式调用

function fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) {
resolve({ id, name: "张三" });
} else {
reject("用户不存在");
}
}, 500);
});
}

fetchUser(1)
.then(user => {
console.log("获取用户:", user);
return fetchUser(2); // 返回新的 Promise
})
.then(user => {
console.log("获取用户:", user);
})
.catch(err => console.error(err));

async/await

async/await 是 Promise 的语法糖,让异步代码看起来像同步代码。

基本用法

async function fetchData() {
const result = await someAsyncFunction();
console.log(result);
return result;
}

async 函数

// async 函数自动返回 Promise
async function getData() {
return { name: "张三", age: 20 };
}

// 等同于
function getData() {
return Promise.resolve({ name: "张三", age: 20 });
}

getData().then(data => console.log(data));

await 表达式

function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

async function example() {
console.log("开始");

await delay(1000); // 等待 Promise 完成
console.log("1秒后");

await delay(1000);
console.log("再过1秒");
}

example();

错误处理

async function fetchUser(id) {
try {
const user = await getUser(id);
const posts = await getPosts(user.id);
return { user, posts };
} catch (error) {
console.error("获取失败:", error);
return null;
}
}

// 使用 catch 捕获
async function main() {
const result = await fetchUser(1).catch(err => {
console.error(err);
return {};
});
}

顺序执行 vs 并行执行

// 顺序执行 - 需要等待上一个完成
async function sequential() {
const result1 = await fetch1();
const result2 = await fetch2();
const result3 = await fetch3();
console.log(result1, result2, result3);
}

// 并行执行 - 同时发起请求
async function parallel() {
const [result1, result2, result3] = await Promise.all([
fetch1(),
fetch2(),
fetch3()
]);
console.log(result1, result2, result3);
}

实际应用示例

发送 HTTP 请求

async function fetchData(url) {
try {
const response = await fetch(url);

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const data = await response.json();
return data;
} catch (error) {
console.error("请求失败:", error);
throw error;
}
}

async function loadUserData() {
try {
const user = await fetchData("/api/user/1");
const posts = await fetchData("/api/posts");
console.log(user, posts);
} catch (error) {
console.error("加载失败:", error);
}
}

并行请求多个 API

async function getDashboardData() {
try {
const [user, orders, notifications] = await Promise.all([
fetch("/api/user").then(r => r.json()),
fetch("/api/orders").then(r => r.json()),
fetch("/api/notifications").then(r => r.json())
]);

return { user, orders, notifications };
} catch (error) {
console.error("加载仪表盘数据失败:", error);
throw error;
}
}

循环中的 async/await

// 顺序处理
async function processSequentially(ids) {
const results = [];
for (const id of ids) {
const result = await fetchData(id);
results.push(result);
}
return results;
}

// 并行处理
async function processInParallel(ids) {
const promises = ids.map(id => fetchData(id));
return Promise.all(promises);
}

高级用法

立即执行函数

(async () => {
const data = await fetchData();
console.log(data);
})();

Promise 限制并发数

async function parallelLimit(tasks, limit) {
const results = [];
const executing = [];

for (const task of tasks) {
const promise = task().then(result => {
results.push(result);
executing.splice(executing.indexOf(promise), 1);
});

results.push(promise);
executing.push(promise);

if (executing.length >= limit) {
await Promise.race(executing);
}
}

return Promise.all(results);
}

超时处理

function withTimeout(promise, ms) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error("超时")), ms)
)
]);
}

async function fetchWithTimeout() {
try {
const data = await withTimeout(fetch("/api/data"), 5000);
return data;
} catch (error) {
if (error.message === "超时") {
console.log("请求超时");
}
throw error;
}
}

重试机制

async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error("请求失败");
return await response.json();
} catch (error) {
if (i === retries - 1) throw error;
console.log(`重试 ${i + 1}/${retries}`);
await new Promise(r => setTimeout(r, 1000 * (i + 1)));
}
}
}

小结

  1. 同步代码按顺序执行,异步代码不阻塞等待结果
  2. Promise 提供了更好的异步处理方式
  3. Promise 有 pending、fulfilled、rejected 三种状态
  4. Promise.all、Promise.race 等方法用于 Promise 组合
  5. async/await 是 Promise 的语法糖,让异步代码更易读
  6. 使用 try/catch 处理 async/await 的错误

练习

  1. 将回调函数转换为 Promise 形式
  2. 实现一个支持超时的 fetch 函数
  3. 实现一个请求重试函数
  4. 实现一个并发限制函数
  5. 模拟实现 Promise.all 方法