Skip to content

异步编程

JavaScript 是单线程语言,异步编程是处理耗时操作的关键技术。本章将深入介绍回调函数、Promise、async/await 等异步编程方式。

为什么需要异步

JavaScript 单线程特性

javascript
// JavaScript 是单线程的,代码按顺序执行
console.log('开始');
console.log('中间');
console.log('结束');
// 输出:开始 -> 中间 -> 结束

// 同步操作会阻塞后续代码
function syncOperation() {
    let start = Date.now();
    while (Date.now() - start < 2000) {
        // 模拟耗时操作,阻塞 2 秒
    }
    console.log('耗时操作完成');
}

console.log('开始');
syncOperation();  // 阻塞 2 秒
console.log('结束');
// 输出:开始 -> (等待 2 秒) -> 耗时操作完成 -> 结束

// 异步操作不会阻塞
console.log('开始');
setTimeout(() => {
    console.log('异步操作完成');
}, 2000);
console.log('结束');
// 输出:开始 -> 结束 -> (2 秒后) -> 异步操作完成

同步 vs 异步

javascript
// 同步代码:按顺序执行,等待完成
function syncAdd(a, b) {
    return a + b;
}
let result = syncAdd(1, 2);  // 立即得到结果
console.log(result);  // 3

// 异步代码:不等待完成,通过回调获取结果
function asyncAdd(a, b, callback) {
    setTimeout(() => {
        callback(a + b);
    }, 1000);
}
asyncAdd(1, 2, (result) => {
    console.log(result);  // 1 秒后输出 3
});
console.log('这行先执行');  // 先输出

回调函数

基本概念

javascript
// 回调函数:作为参数传递给其他函数的函数
function greet(name, callback) {
    console.log('你好,' + name);
    callback();
}

greet('张三', function() {
    console.log('回调函数执行');
});

// 数组方法中的回调
let numbers = [1, 2, 3, 4, 5];

// forEach
numbers.forEach(function(num, index) {
    console.log(`索引 ${index}: ${num}`);
});

// map
let doubled = numbers.map(function(num) {
    return num * 2;
});

// filter
let evens = numbers.filter(function(num) {
    return num % 2 === 0;
});

// 箭头函数简化
numbers.forEach(num => console.log(num));
let tripled = numbers.map(num => num * 3);

回调地狱

javascript
// 回调地狱:多层嵌套的回调函数
function getUser(userId, callback) {
    setTimeout(() => {
        callback({ id: userId, name: '张三' });
    }, 1000);
}

function getOrders(user, callback) {
    setTimeout(() => {
        callback([
            { id: 1, product: '手机' },
            { id: 2, product: '电脑' }
        ]);
    }, 1000);
}

function getOrderDetails(order, callback) {
    setTimeout(() => {
        callback({ ...order, price: 5999, quantity: 1 });
    }, 1000);
}

// 回调地狱
getUser(1, function(user) {
    console.log('用户:', user);
    getOrders(user, function(orders) {
        console.log('订单:', orders);
        getOrderDetails(orders[0], function(details) {
            console.log('订单详情:', details);
            // 继续嵌套...
        });
    });
});

// 问题:
// 1. 代码难以阅读和维护
// 2. 错误处理复杂
// 3. 难以复用

解决回调地狱

javascript
// 方法 1:拆分函数
function handleUser(user) {
    console.log('用户:', user);
    getOrders(user, handleOrders);
}

function handleOrders(orders) {
    console.log('订单:', orders);
    getOrderDetails(orders[0], handleDetails);
}

function handleDetails(details) {
    console.log('订单详情:', details);
}

getUser(1, handleUser);

// 方法 2:使用 Promise(推荐)
getUser(1)
    .then(user => {
        console.log('用户:', user);
        return getOrders(user);
    })
    .then(orders => {
        console.log('订单:', orders);
        return getOrderDetails(orders[0]);
    })
    .then(details => {
        console.log('订单详情:', details);
    })
    .catch(error => {
        console.log('错误:', error);
    });

// 方法 3:使用 async/await(最推荐)
async function processOrder() {
    try {
        const user = await getUser(1);
        console.log('用户:', user);
        
        const orders = await getOrders(user);
        console.log('订单:', orders);
        
        const details = await getOrderDetails(orders[0]);
        console.log('订单详情:', details);
    } catch (error) {
        console.log('错误:', error);
    }
}

processOrder();

Promise

创建 Promise

javascript
// Promise 构造函数
const promise = new Promise((resolve, reject) => {
    // resolve:成功时调用
    // reject:失败时调用
    
    let success = true;
    
    if (success) {
        resolve('操作成功');
    } else {
        reject('操作失败');
    }
});

// Promise 状态
// pending:进行中(初始状态)
// fulfilled:已成功
// rejected:已失败

// 状态只能改变一次
const p = new Promise((resolve, reject) => {
    resolve('成功');
    reject('失败');  // 无效,状态已经改变
});

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

// 使用
delay(1000).then(() => {
    console.log('1 秒后执行');
});

// 封装 AJAX 为 Promise
function ajax(url) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', url);
        xhr.onload = () => {
            if (xhr.status === 200) {
                resolve(JSON.parse(xhr.responseText));
            } else {
                reject(new Error(`HTTP ${xhr.status}`));
            }
        };
        xhr.onerror = () => reject(new Error('网络错误'));
        xhr.send();
    });
}

使用 Promise

javascript
const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        let success = Math.random() > 0.5;
        if (success) {
            resolve('成功');
        } else {
            reject('失败');
        }
    }, 1000);
});

// then:处理成功
// catch:处理失败
// finally:无论成功失败都执行
promise
    .then(result => {
        console.log('成功:', result);
    })
    .catch(error => {
        console.log('失败:', error);
    })
    .finally(() => {
        console.log('完成');
    });

// then 可以接受两个参数
promise.then(
    result => console.log('成功:', result),
    error => console.log('失败:', error)
);

// 链式调用
promise
    .then(result => {
        console.log('第一步:', result);
        return '处理后的结果';
    })
    .then(result => {
        console.log('第二步:', result);
        return new Promise(resolve => {
            setTimeout(() => resolve('异步结果'), 1000);
        });
    })
    .then(result => {
        console.log('第三步:', result);
    });

// 错误冒泡
Promise.resolve()
    .then(() => {
        throw new Error('失败');
    })
    .then(() => {
        console.log('不会执行');
    })
    .catch(error => {
        console.log('捕获错误:', error.message);
    });

Promise 静态方法

javascript
// Promise.resolve():返回成功的 Promise
const p1 = Promise.resolve('成功');
p1.then(result => console.log(result));  // 成功

// Promise.reject():返回失败的 Promise
const p2 = Promise.reject('失败');
p2.catch(error => console.log(error));  // 失败

// Promise.all():所有都成功才成功
const p3 = Promise.resolve(1);
const p4 = Promise.resolve(2);
const p5 = Promise.resolve(3);

Promise.all([p3, p4, p5])
    .then(results => {
        console.log(results);  // [1, 2, 3]
    })
    .catch(error => {
        console.log('有一个失败:', error);
    });

// 应用:同时请求多个接口
Promise.all([
    fetch('/api/users'),
    fetch('/api/posts'),
    fetch('/api/comments')
])
.then(responses => Promise.all(responses.map(r => r.json())))
.then(([users, posts, comments]) => {
    console.log('所有数据加载完成');
});

// Promise.race():第一个完成的结果
Promise.race([
    new Promise(resolve => setTimeout(() => resolve('慢'), 2000)),
    new Promise(resolve => setTimeout(() => resolve('快'), 1000))
])
.then(result => console.log(result));  // 快

// 应用:请求超时
function fetchWithTimeout(url, timeout = 5000) {
    return Promise.race([
        fetch(url),
        new Promise((_, reject) => {
            setTimeout(() => reject(new Error('请求超时')), timeout);
        })
    ]);
}

// Promise.allSettled():等待所有完成(无论成功失败)
Promise.allSettled([
    Promise.resolve('成功1'),
    Promise.reject('失败'),
    Promise.resolve('成功2')
])
.then(results => {
    results.forEach(result => {
        if (result.status === 'fulfilled') {
            console.log('成功:', result.value);
        } else {
            console.log('失败:', result.reason);
        }
    });
});

// Promise.any():任意一个成功就成功(ES2021)
Promise.any([
    Promise.reject('失败1'),
    Promise.resolve('成功'),
    Promise.reject('失败2')
])
.then(result => console.log(result));  // 成功

async/await

基本用法

javascript
// async 函数返回 Promise
async function greet() {
    return '你好';
}

// 等价于
function greet() {
    return Promise.resolve('你好');
}

// 调用 async 函数
greet().then(result => console.log(result));  // 你好

// await 等待 Promise 完成
async function getData() {
    const response = await fetch('/api/data');
    const data = await response.json();
    return data;
}

// await 只能在 async 函数内使用
// function wrong() {
//     await fetch('/api/data');  // 错误
// }

// 顶层 await(ES2022,模块中可用)
// const data = await fetch('/api/data');

错误处理

javascript
// try...catch 处理错误
async function fetchData() {
    try {
        const response = await fetch('/api/data');
        
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}`);
        }
        
        const data = await response.json();
        return data;
    } catch (error) {
        console.log('错误:', error.message);
        throw error;  // 可以继续抛出
    }
}

// 使用 catch 方法
fetchData().catch(error => {
    console.log('外部捕获:', error);
});

// 封装错误处理
async function tryCatch(promise) {
    try {
        const data = await promise;
        return [null, data];
    } catch (error) {
        return [error, null];
    }
}

// 使用
const [error, data] = await tryCatch(fetch('/api/data'));
if (error) {
    console.log('错误:', error);
} else {
    console.log('数据:', data);
}

并行执行

javascript
// 串行执行(一个接一个)
async function serial() {
    const user = await fetch('/api/user').then(r => r.json());
    const posts = await fetch('/api/posts').then(r => r.json());
    const comments = await fetch('/api/comments').then(r => r.json());
    return { user, posts, comments };
}

// 并行执行(同时进行)
async function parallel() {
    const [user, posts, comments] = await Promise.all([
        fetch('/api/user').then(r => r.json()),
        fetch('/api/posts').then(r => r.json()),
        fetch('/api/comments').then(r => r.json())
    ]);
    return { user, posts, comments };
}

// 使用 Promise.all
async function fetchAll() {
    const urls = ['/api/user', '/api/posts', '/api/comments'];
    const responses = await Promise.all(urls.map(url => 
        fetch(url).then(r => r.json())
    ));
    return responses;
}

// for...of 循环中使用 await(串行)
async function processItems(items) {
    for (const item of items) {
        await processItem(item);
    }
}

// 并行处理数组
async function processItemsParallel(items) {
    await Promise.all(items.map(item => processItem(item)));
}

循环中的异步

javascript
// for...of 循环(串行)
async function processArray(array) {
    for (const item of array) {
        await processItem(item);
    }
}

// forEach 不支持 await
async function wrong() {
    [1, 2, 3].forEach(async (item) => {
        await processItem(item);  // 不会等待
    });
    console.log('完成');  // 先输出
}

// map 返回 Promise 数组
async function processWithMap(array) {
    const promises = array.map(item => processItem(item));
    await Promise.all(promises);
}

// for 循环
async function processWithFor(array) {
    for (let i = 0; i < array.length; i++) {
        await processItem(array[i]);
    }
}

// for await...of(异步迭代)
async function* asyncGenerator() {
    yield Promise.resolve(1);
    yield Promise.resolve(2);
    yield Promise.resolve(3);
}

async function processAsyncIterator() {
    for await (const value of asyncGenerator()) {
        console.log(value);
    }
}

事件循环

执行机制

javascript
// JavaScript 执行顺序
console.log('1. 同步代码');

setTimeout(() => {
    console.log('2. 宏任务');
}, 0);

Promise.resolve().then(() => {
    console.log('3. 微任务');
});

console.log('4. 同步代码');

// 输出顺序:
// 1. 同步代码
// 4. 同步代码
// 3. 微任务
// 2. 宏任务

// 事件循环流程:
// 1. 执行同步代码
// 2. 执行微任务队列中的所有任务
// 3. 执行一个宏任务
// 4. 重复步骤 2-3

宏任务和微任务

javascript
// 宏任务(Macro Task)
// - setTimeout
// - setInterval
// - setImmediate(Node.js)
// - I/O
// - UI 渲染

// 微任务(Micro Task)
// - Promise.then/catch/finally
// - process.nextTick(Node.js)
// - MutationObserver
// - queueMicrotask

// 执行顺序示例
async function test() {
    console.log('1. async 函数开始');
    
    await Promise.resolve();
    console.log('2. await 后面');
    
    console.log('3. async 函数结束');
}

console.log('0. 同步开始');

test();

console.log('4. 同步结束');

Promise.resolve().then(() => {
    console.log('5. Promise 微任务');
});

setTimeout(() => {
    console.log('6. setTimeout 宏任务');
}, 0);

// 输出顺序:
// 0. 同步开始
// 1. async 函数开始
// 4. 同步结束
// 2. await 后面
// 3. async 函数结束
// 5. Promise 微任务
// 6. setTimeout 宏任务

queueMicrotask

javascript
// queueMicrotask:添加微任务
queueMicrotask(() => {
    console.log('微任务');
});

// 等价于
Promise.resolve().then(() => {
    console.log('微任务');
});

// 使用场景:确保代码在当前任务完成后执行
function doSomething() {
    // 执行一些操作
    
    // 确保回调在当前任务完成后执行
    queueMicrotask(() => {
        console.log('当前任务完成后执行');
    });
}

实战案例

请求重试

javascript
// 带重试的请求
async function fetchWithRetry(url, options = {}, retries = 3) {
    for (let i = 0; i < retries; i++) {
        try {
            const response = await fetch(url, options);
            if (!response.ok) {
                throw new Error(`HTTP ${response.status}`);
            }
            return await response.json();
        } catch (error) {
            console.log(`第 ${i + 1} 次尝试失败:`, error.message);
            
            if (i === retries - 1) {
                throw error;
            }
            
            // 等待一段时间后重试
            await new Promise(resolve => 
                setTimeout(resolve, 1000 * Math.pow(2, i))
            );
        }
    }
}

// 使用
fetchWithRetry('/api/data')
    .then(data => console.log(data))
    .catch(error => console.log('所有重试失败:', error));

并发控制

javascript
// 限制并发数量
async function limitConcurrency(tasks, limit) {
    const results = [];
    const executing = new Set();
    
    for (const task of tasks) {
        const promise = Promise.resolve().then(() => task());
        results.push(promise);
        executing.add(promise);
        
        const cleanup = () => executing.delete(promise);
        promise.then(cleanup, cleanup);
        
        if (executing.size >= limit) {
            await Promise.race(executing);
        }
    }
    
    return Promise.all(results);
}

// 使用
const tasks = urls.map(url => () => fetch(url));
const results = await limitConcurrency(tasks, 3);  // 最多 3 个并发

// 简化版本:并发池
class ConcurrencyPool {
    constructor(limit) {
        this.limit = limit;
        this.running = 0;
        this.queue = [];
    }
    
    async run(task) {
        if (this.running >= this.limit) {
            await new Promise(resolve => this.queue.push(resolve));
        }
        
        this.running++;
        try {
            return await task();
        } finally {
            this.running--;
            const next = this.queue.shift();
            if (next) next();
        }
    }
}

// 使用
const pool = new ConcurrencyPool(3);
const results = await Promise.all(
    urls.map(url => pool.run(() => fetch(url)))
);

异步缓存

javascript
// 异步缓存装饰器
function asyncCache(fn) {
    const cache = new Map();
    
    return async function(...args) {
        const key = JSON.stringify(args);
        
        if (cache.has(key)) {
            return cache.get(key);
        }
        
        const promise = fn.apply(this, args);
        cache.set(key, promise);
        
        try {
            return await promise;
        } catch (error) {
            cache.delete(key);
            throw error;
        }
    };
}

// 使用
const fetchUser = asyncCache(async (id) => {
    const response = await fetch(`/api/users/${id}`);
    return response.json();
});

// 第一次调用会请求
const user1 = await fetchUser(1);
// 第二次调用直接返回缓存
const user2 = await fetchUser(1);

异步队列

javascript
// 异步队列
class AsyncQueue {
    constructor() {
        this.queue = [];
        this.processing = false;
    }
    
    async enqueue(task) {
        return new Promise((resolve, reject) => {
            this.queue.push({ task, resolve, reject });
            this.process();
        });
    }
    
    async process() {
        if (this.processing || this.queue.length === 0) {
            return;
        }
        
        this.processing = true;
        
        while (this.queue.length > 0) {
            const { task, resolve, reject } = this.queue.shift();
            
            try {
                const result = await task();
                resolve(result);
            } catch (error) {
                reject(error);
            }
        }
        
        this.processing = false;
    }
}

// 使用
const queue = new AsyncQueue();

queue.enqueue(() => fetch('/api/1'));
queue.enqueue(() => fetch('/api/2'));
queue.enqueue(() => fetch('/api/3'));

小结

本章学习了 JavaScript 异步编程的核心知识:

  • 异步概念:单线程、同步 vs 异步
  • 回调函数:基本用法、回调地狱
  • Promise:创建、使用、静态方法、链式调用
  • async/await:语法糖、错误处理、并行执行
  • 事件循环:宏任务、微任务、执行顺序
  • 实战案例:请求重试、并发控制、异步缓存

掌握异步编程是成为 JavaScript 开发者的必备技能,建议多练习实际项目中的异步场景。