JavaScript 异步编程
异步编程是 JavaScript 的核心特性之一,它允许程序在等待耗时操作(如网络请求、文件读写、定时器等)完成时继续执行其他代码,从而提高程序的响应性和性能。理解异步编程对于构建现代 Web 应用程序至关重要。在本章节中,我们将深入学习 JavaScript 中的各种异步编程技术。
什么是异步编程
异步编程是指程序在执行耗时操作时不会阻塞后续代码的执行,而是继续执行其他任务,当耗时操作完成后通过回调、Promise 或 async/await 等方式处理结果。
同步 vs 异步
javascript
// 同步代码示例
console.log("1. 开始执行");
// 这会阻塞后续代码执行(在实际应用中不会这样写)
function syncDelay(ms) {
const start = Date.now();
while (Date.now() - start < ms) {
// 空循环,阻塞执行
}
}
syncDelay(2000); // 阻塞 2 秒
console.log("2. 同步延迟结束");
// 异步代码示例
console.log("1. 开始执行");
setTimeout(() => {
console.log("3. 异步操作完成");
}, 2000);
console.log("2. 继续执行其他代码");回调函数(Callback)
回调函数是最早的异步编程方式,但容易导致回调地狱问题。
基本回调函数
javascript
// 简单回调示例
function fetchData(callback) {
setTimeout(() => {
const data = { id: 1, name: "张三" };
callback(null, data); // 第一个参数是错误,第二个是数据
}, 1000);
}
fetchData((error, data) => {
if (error) {
console.error("获取数据失败:", error);
} else {
console.log("获取到数据:", data);
}
});回调地狱问题
javascript
// 回调地狱示例
function step1(callback) {
setTimeout(() => {
console.log("步骤1完成");
callback(null, "result1");
}, 1000);
}
function step2(data, callback) {
setTimeout(() => {
console.log("步骤2完成,使用数据:", data);
callback(null, "result2");
}, 1000);
}
function step3(data, callback) {
setTimeout(() => {
console.log("步骤3完成,使用数据:", data);
callback(null, "result3");
}, 1000);
}
// 回调地狱
step1((error1, result1) => {
if (error1) {
console.error("步骤1失败:", error1);
return;
}
step2(result1, (error2, result2) => {
if (error2) {
console.error("步骤2失败:", error2);
return;
}
step3(result2, (error3, result3) => {
if (error3) {
console.error("步骤3失败:", error3);
return;
}
console.log("所有步骤完成:", result3);
});
});
});Promise
Promise 是 ES6 引入的异步编程解决方案,解决了回调地狱问题。
Promise 基础
javascript
// 创建 Promise
const myPromise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve("操作成功!");
} else {
reject(new Error("操作失败!"));
}
}, 1000);
});
// 使用 Promise
myPromise
.then(result => {
console.log("成功:", result);
})
.catch(error => {
console.error("失败:", error.message);
})
.finally(() => {
console.log("操作完成(无论成功还是失败)");
});Promise 状态
javascript
// Promise 的三种状态
function demonstratePromiseStates() {
// pending(待定)状态
const pendingPromise = new Promise(() => {
// 还没有 resolve 或 reject
});
console.log("待定状态:", pendingPromise);
// fulfilled(已成功)状态
const fulfilledPromise = Promise.resolve("成功的结果");
console.log("成功状态:", fulfilledPromise);
// rejected(已失败)状态
const rejectedPromise = Promise.reject(new Error("失败的原因"));
console.log("失败状态:", rejectedPromise);
}
demonstratePromiseStates();Promise 链式调用
javascript
// Promise 链式调用
function fetchUserData(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (userId > 0) {
resolve({ id: userId, name: `用户${userId}` });
} else {
reject(new Error("无效的用户ID"));
}
}, 1000);
});
}
function fetchUserPosts(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (userId > 0) {
resolve([
{ id: 1, title: "第一篇文章", userId: userId },
{ id: 2, title: "第二篇文章", userId: userId }
]);
} else {
reject(new Error("获取文章失败"));
}
}, 1000);
});
}
// 链式调用
fetchUserData(1)
.then(user => {
console.log("获取到用户:", user);
return fetchUserPosts(user.id);
})
.then(posts => {
console.log("获取到文章:", posts);
return posts.length;
})
.then(count => {
console.log("文章数量:", count);
})
.catch(error => {
console.error("操作失败:", error.message);
});Promise 静态方法
javascript
// Promise.resolve()
const resolvedPromise = Promise.resolve("立即成功的 Promise");
resolvedPromise.then(result => {
console.log("resolve 结果:", result);
});
// Promise.reject()
const rejectedPromise = Promise.reject(new Error("立即失败的 Promise"));
rejectedPromise.catch(error => {
console.error("reject 错误:", error.message);
});
// Promise.all() - 所有 Promise 都成功才成功
const promises = [
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3)
];
Promise.all(promises)
.then(results => {
console.log("所有 Promise 完成:", results); // [1, 2, 3]
})
.catch(error => {
console.error("其中一个 Promise 失败:", error);
});
// Promise.all() 失败示例
const failingPromises = [
Promise.resolve(1),
Promise.reject(new Error("第二个 Promise 失败")),
Promise.resolve(3)
];
Promise.all(failingPromises)
.then(results => {
console.log("这不会执行");
})
.catch(error => {
console.error("捕获到错误:", error.message); // "第二个 Promise 失败"
});
// Promise.allSettled() - 等待所有 Promise 完成(ES2020)
Promise.allSettled(failingPromises)
.then(results => {
console.log("所有 Promise 都已完成:", results);
/*
[
{ status: "fulfilled", value: 1 },
{ status: "rejected", reason: Error("第二个 Promise 失败") },
{ status: "fulfilled", value: 3 }
]
*/
});
// Promise.race() - 第一个完成的 Promise 决定结果
const fastPromise = new Promise(resolve => {
setTimeout(() => resolve("快速 Promise"), 100);
});
const slowPromise = new Promise(resolve => {
setTimeout(() => resolve("慢速 Promise"), 1000);
});
Promise.race([fastPromise, slowPromise])
.then(result => {
console.log("最先完成的 Promise:", result); // "快速 Promise"
});
// Promise.any() - 第一个成功的 Promise 决定结果(ES2021)
Promise.any([fastPromise, slowPromise])
.then(result => {
console.log("第一个成功的 Promise:", result); // "快速 Promise"
});async/await
async/await 是 ES2017 引入的语法糖,让异步代码看起来像同步代码。
基本语法
javascript
// async 函数
async function fetchData() {
try {
const response = await fetch("https://api.example.com/data");
const data = await response.json();
return data;
} catch (error) {
console.error("获取数据失败:", error);
throw error;
}
}
// 调用 async 函数
fetchData()
.then(data => {
console.log("获取到数据:", data);
})
.catch(error => {
console.error("处理失败:", error);
});错误处理
javascript
// async/await 错误处理
async function processUserData() {
try {
console.log("开始获取用户数据");
const user = await fetchUserData(1);
console.log("用户数据:", user);
console.log("开始获取用户文章");
const posts = await fetchUserPosts(user.id);
console.log("用户文章:", posts);
return { user, posts };
} catch (error) {
console.error("处理用户数据时出错:", error.message);
throw new Error("用户数据处理失败:" + error.message);
}
}
// 使用示例
processUserData()
.then(result => {
console.log("处理完成:", result);
})
.catch(error => {
console.error("最终失败:", error.message);
});并行执行
javascript
// 串行执行(较慢)
async function serialExecution() {
console.time("串行执行");
const user = await fetchUserData(1);
const posts = await fetchUserPosts(1);
const comments = await fetchUserComments(1);
console.timeEnd("串行执行");
return { user, posts, comments };
}
// 并行执行(较快)
async function parallelExecution() {
console.time("并行执行");
const [user, posts, comments] = await Promise.all([
fetchUserData(1),
fetchUserPosts(1),
fetchUserComments(1)
]);
console.timeEnd("并行执行");
return { user, posts, comments };
}
// 混合执行(部分并行)
async function mixedExecution() {
console.time("混合执行");
// 先获取用户数据
const user = await fetchUserData(1);
// 然后并行获取文章和评论
const [posts, comments] = await Promise.all([
fetchUserPosts(user.id),
fetchUserComments(user.id)
]);
console.timeEnd("混合执行");
return { user, posts, comments };
}
// 模拟获取用户评论
function fetchUserComments(userId) {
return new Promise(resolve => {
setTimeout(() => {
resolve([
{ id: 1, content: "第一条评论", userId: userId },
{ id: 2, content: "第二条评论", userId: userId }
]);
}, 1000);
});
}异步迭代器和生成器
异步生成器
javascript
// 异步生成器函数
async function* asyncNumberGenerator() {
let i = 0;
while (i < 5) {
await new Promise(resolve => setTimeout(resolve, 1000));
yield i++;
}
}
// 使用异步生成器
async function useAsyncGenerator() {
for await (const number of asyncNumberGenerator()) {
console.log("生成的数字:", number);
}
}
// useAsyncGenerator();异步迭代
javascript
// 自定义异步可迭代对象
class AsyncDataLoader {
constructor(data) {
this.data = data;
}
async *[Symbol.asyncIterator]() {
for (const item of this.data) {
// 模拟异步加载
await new Promise(resolve => setTimeout(resolve, 500));
yield item;
}
}
}
// 使用异步迭代器
async function useAsyncIterator() {
const loader = new AsyncDataLoader([1, 2, 3, 4, 5]);
for await (const item of loader) {
console.log("加载的数据:", item);
}
}
// useAsyncIterator();微任务和宏任务
理解 JavaScript 的事件循环对异步编程很重要。
javascript
// 事件循环示例
console.log("1. 同步代码");
setTimeout(() => {
console.log("2. 宏任务(setTimeout)");
}, 0);
Promise.resolve().then(() => {
console.log("3. 微任务(Promise)");
});
console.log("4. 同步代码结束");
// 输出顺序:
// 1. 同步代码
// 4. 同步代码结束
// 3. 微任务(Promise)
// 2. 宏任务(setTimeout)异步编程最佳实践
1. 错误处理
javascript
// 统一的错误处理
class AsyncErrorHandler {
static async handle(asyncFunction, ...args) {
try {
const result = await asyncFunction(...args);
return { success: true, data: result };
} catch (error) {
console.error("异步操作失败:", error);
return { success: false, error: error.message };
}
}
static async retry(asyncFunction, maxRetries = 3, delay = 1000) {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
return await asyncFunction();
} catch (error) {
lastError = error;
console.log(`第 ${i + 1} 次尝试失败:${error.message}`);
if (i < maxRetries - 1) {
await this.delay(delay * (i + 1));
}
}
}
throw new Error(`经过 ${maxRetries} 次重试后仍然失败:${lastError.message}`);
}
static delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// 使用示例
async function unreliableApiCall() {
if (Math.random() > 0.7) {
return "API 调用成功";
} else {
throw new Error("API 调用失败");
}
}
// AsyncErrorHandler.retry(unreliableApiCall, 3, 1000)
// .then(result => console.log("最终结果:", result))
// .catch(error => console.error("最终失败:", error.message));2. 超时控制
javascript
// Promise 超时控制
class PromiseWithTimeout {
static async timeout(promise, ms) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error(`操作超时(${ms}ms)`)), ms);
});
return Promise.race([promise, timeoutPromise]);
}
static async withTimeout(asyncFunction, ms, ...args) {
const promise = asyncFunction(...args);
return this.timeout(promise, ms);
}
}
// 使用示例
async function slowOperation() {
await new Promise(resolve => setTimeout(resolve, 3000));
return "慢操作完成";
}
// PromiseWithTimeout.withTimeout(slowOperation, 2000)
// .then(result => console.log("结果:", result))
// .catch(error => console.error("错误:", error.message));3. 并发控制
javascript
// 限制并发数量
class ConcurrencyController {
constructor(limit = 3) {
this.limit = limit;
this.running = 0;
this.queue = [];
}
async add(asyncFunction) {
return new Promise((resolve, reject) => {
this.queue.push({
asyncFunction,
resolve,
reject
});
this.process();
});
}
async process() {
if (this.running >= this.limit || this.queue.length === 0) {
return;
}
this.running++;
const { asyncFunction, resolve, reject } = this.queue.shift();
try {
const result = await asyncFunction();
resolve(result);
} catch (error) {
reject(error);
} finally {
this.running--;
this.process();
}
}
}
// 使用示例
const controller = new ConcurrencyController(2);
async function fetchData(id) {
console.log(`开始获取数据 ${id}`);
await new Promise(resolve => setTimeout(resolve, 1000));
console.log(`数据 ${id} 获取完成`);
return `数据 ${id}`;
}
// 并发执行多个任务,但限制同时执行的数量
async function runConcurrentTasks() {
const tasks = Array.from({ length: 10 }, (_, i) =>
controller.add(() => fetchData(i + 1))
);
const results = await Promise.all(tasks);
console.log("所有任务完成:", results);
}
// runConcurrentTasks();实际应用示例
1. API 客户端
javascript
class ApiClient {
constructor(baseURL, options = {}) {
this.baseURL = baseURL;
this.defaultOptions = {
timeout: 5000,
retries: 3,
...options
};
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const config = { ...this.defaultOptions, ...options };
let lastError;
for (let i = 0; i < config.retries; i++) {
try {
const response = await this.fetchWithTimeout(url, config);
return await this.handleResponse(response);
} catch (error) {
lastError = error;
console.log(`第 ${i + 1} 次请求失败:${error.message}`);
if (i < config.retries - 1) {
await this.delay(1000 * (i + 1));
}
}
}
throw new Error(`请求失败(经过 ${config.retries} 次重试):${lastError.message}`);
}
async fetchWithTimeout(url, options) {
const { timeout, ...fetchOptions } = options;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
...fetchOptions,
signal: controller.signal
});
return response;
} finally {
clearTimeout(timeoutId);
}
}
async handleResponse(response) {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const contentType = response.headers.get("content-type");
if (contentType && contentType.includes("application/json")) {
return await response.json();
}
return await response.text();
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 便捷方法
async get(endpoint, options = {}) {
return this.request(endpoint, { method: "GET", ...options });
}
async post(endpoint, data, options = {}) {
return this.request(endpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
...options
});
}
async put(endpoint, data, options = {}) {
return this.request(endpoint, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
...options
});
}
async delete(endpoint, options = {}) {
return this.request(endpoint, { method: "DELETE", ...options });
}
}
// 使用示例
const api = new ApiClient("https://jsonplaceholder.typicode.com");
// api.get("/users/1")
// .then(user => console.log("用户信息:", user))
// .catch(error => console.error("获取用户失败:", error));
// api.post("/posts", {
// title: "新文章",
// body: "文章内容",
// userId: 1
// }).then(post => console.log("创建的文章:", post))
// .catch(error => console.error("创建文章失败:", error));2. 数据加载管理器
javascript
class DataLoader {
constructor() {
this.cache = new Map();
this.loading = new Map();
}
async load(key, loaderFunction, options = {}) {
const { cache = true, force = false } = options;
// 检查缓存
if (cache && !force && this.cache.has(key)) {
console.log(`从缓存获取数据:${key}`);
return this.cache.get(key);
}
// 检查是否正在加载
if (this.loading.has(key)) {
console.log(`等待正在进行的加载:${key}`);
return this.loading.get(key);
}
// 开始加载
console.log(`开始加载数据:${key}`);
const loadingPromise = this.loadAndCache(key, loaderFunction, cache);
this.loading.set(key, loadingPromise);
try {
const result = await loadingPromise;
return result;
} finally {
this.loading.delete(key);
}
}
async loadAndCache(key, loaderFunction, cache) {
try {
const data = await loaderFunction();
if (cache) {
this.cache.set(key, data);
console.log(`数据已缓存:${key}`);
}
return data;
} catch (error) {
console.error(`加载数据失败 ${key}:`, error);
throw error;
}
}
clearCache(key) {
if (key) {
this.cache.delete(key);
console.log(`清除缓存:${key}`);
} else {
this.cache.clear();
console.log("清除所有缓存");
}
}
getCacheStats() {
return {
cacheSize: this.cache.size,
loadingCount: this.loading.size,
cachedKeys: Array.from(this.cache.keys())
};
}
// 批量加载
async loadMultiple(loaders, options = {}) {
const { concurrency = 5 } = options;
const results = {};
const entries = Object.entries(loaders);
// 分批处理
for (let i = 0; i < entries.length; i += concurrency) {
const batch = entries.slice(i, i + concurrency);
const batchPromises = batch.map(([key, loader]) =>
this.load(key, loader).then(result => ({ key, result }))
);
const batchResults = await Promise.all(batchPromises);
batchResults.forEach(({ key, result }) => {
results[key] = result;
});
}
return results;
}
}
// 使用示例
const dataLoader = new DataLoader();
// 模拟数据加载函数
async function loadUserData() {
await new Promise(resolve => setTimeout(resolve, 1000));
return { id: 1, name: "张三", email: "zhangsan@example.com" };
}
async function loadUserPosts() {
await new Promise(resolve => setTimeout(resolve, 1500));
return [
{ id: 1, title: "第一篇文章" },
{ id: 2, title: "第二篇文章" }
];
}
// 加载数据
// dataLoader.load("user", loadUserData)
// .then(user => console.log("用户数据:", user));
// 批量加载
// dataLoader.loadMultiple({
// user: loadUserData,
// posts: loadUserPosts
// }).then(results => {
// console.log("批量加载结果:", results);
// });
// 查看缓存状态
// console.log("缓存统计:", dataLoader.getCacheStats());总结
JavaScript 异步编程的核心要点:
- 基本概念:异步编程允许程序在等待耗时操作时继续执行
- 回调函数:最早的异步方式,但容易导致回调地狱
- Promise:ES6 引入的解决方案,支持链式调用和错误处理
- async/await:ES2017 引入的语法糖,让异步代码看起来像同步代码
- Promise 静态方法:all()、race()、allSettled()、any() 等
- 异步迭代:异步生成器和异步迭代器
- 事件循环:理解微任务和宏任务的执行顺序
- 最佳实践:错误处理、超时控制、并发控制
- 实际应用:API 客户端、数据加载管理器
掌握异步编程是构建现代 JavaScript 应用程序的关键技能。在下一章节中,我们将学习 JavaScript 的表单处理。