Skip to content

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 异步编程的核心要点:

  1. 基本概念:异步编程允许程序在等待耗时操作时继续执行
  2. 回调函数:最早的异步方式,但容易导致回调地狱
  3. Promise:ES6 引入的解决方案,支持链式调用和错误处理
  4. async/await:ES2017 引入的语法糖,让异步代码看起来像同步代码
  5. Promise 静态方法:all()、race()、allSettled()、any() 等
  6. 异步迭代:异步生成器和异步迭代器
  7. 事件循环:理解微任务和宏任务的执行顺序
  8. 最佳实践:错误处理、超时控制、并发控制
  9. 实际应用:API 客户端、数据加载管理器

掌握异步编程是构建现代 JavaScript 应用程序的关键技能。在下一章节中,我们将学习 JavaScript 的表单处理。

本站内容仅供学习和研究使用。