Skip to content

异步编程

1. JavaScript 异步模型

1.1 为什么需要异步?

1.2 异步编程演进


2. Event Loop (事件循环) 🔥

2.1 执行机制

2.2 宏任务 vs 微任务

类型任务来源
宏任务setTimeout, setInterval, setImmediate(Node), I/O, UI事件
微任务Promise.then/catch/finally, queueMicrotask, MutationObserver, process.nextTick(Node)

2.3 执行顺序

javascript
console.log('1');  // 同步

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

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

console.log('4');  // 同步

// 输出: 1, 4, 3, 2

2.4 经典面试题

javascript
console.log('start');

setTimeout(() => {
    console.log('timeout1');
    Promise.resolve().then(() => console.log('promise inside timeout'));
}, 0);

Promise.resolve().then(() => {
    console.log('promise1');
    setTimeout(() => console.log('timeout inside promise'), 0);
});

Promise.resolve().then(() => console.log('promise2'));

console.log('end');

// 输出顺序:
// start
// end
// promise1
// promise2
// timeout1
// promise inside timeout
// timeout inside promise

2.5 Node.js Event Loop

javascript
// Node.js 特有行为
setImmediate(() => console.log('immediate'));
setTimeout(() => console.log('timeout'), 0);

// 输出顺序不确定! (取决于进入 event loop 的时机)

// 但在 I/O 回调中, setImmediate 总是先执行
const fs = require('fs');
fs.readFile('file.txt', () => {
    setImmediate(() => console.log('immediate'));
    setTimeout(() => console.log('timeout'), 0);
});
// 输出: immediate, timeout (确定)

3. Callback (回调函数)

3.1 基本模式

javascript
function fetchData(callback) {
    setTimeout(() => {
        callback(null, { data: 'Hello' });
    }, 1000);
}

fetchData((err, result) => {
    if (err) {
        console.error(err);
        return;
    }
    console.log(result);
});

3.2 回调地狱 (Callback Hell)

javascript
// ❌ 问题: 嵌套过深, 难以维护
getUser(userId, (err, user) => {
    if (err) return handleError(err);
    getOrders(user.id, (err, orders) => {
        if (err) return handleError(err);
        getProducts(orders[0].id, (err, products) => {
            if (err) return handleError(err);
            getDetails(products[0].id, (err, details) => {
                if (err) return handleError(err);
                console.log(details);
            });
        });
    });
});

3.3 Node.js 错误优先回调

javascript
// 约定: 第一个参数是错误对象
fs.readFile('file.txt', (err, data) => {
    if (err) {
        console.error('Error:', err);
        return;
    }
    console.log(data);
});

4. Promise 🔥

4.1 基本用法

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

promise
    .then(result => console.log(result))
    .catch(error => console.error(error))
    .finally(() => console.log('完成'));

4.2 Promise 状态

4.3 链式调用

javascript
fetch('/api/user')
    .then(response => response.json())
    .then(user => fetch(`/api/orders/${user.id}`))
    .then(response => response.json())
    .then(orders => console.log(orders))
    .catch(error => console.error(error));

// 每个 then 返回新的 Promise
// then 回调返回值会被 Promise.resolve() 包装

4.4 静态方法

javascript
// Promise.all - 全部成功才成功
const results = await Promise.all([
    fetch('/api/users'),
    fetch('/api/products'),
    fetch('/api/orders')
]);
// 任一失败则整个失败

// Promise.allSettled - 等待全部完成
const results = await Promise.allSettled([
    fetch('/api/users'),
    Promise.reject('error')
]);
// [{ status: 'fulfilled', value: ... }, { status: 'rejected', reason: 'error' }]

// Promise.race - 返回最先完成的
const result = await Promise.race([
    fetch('/api/users'),
    new Promise((_, reject) => setTimeout(() => reject('timeout'), 5000))
]);

// Promise.any - 返回第一个成功的 (ES2021)
const result = await Promise.any([
    fetch('/api/server1'),
    fetch('/api/server2'),
    fetch('/api/server3')
]);
// 全部失败才失败 (AggregateError)

// Promise.resolve / Promise.reject
Promise.resolve('value');  // 立即 fulfilled
Promise.reject(new Error('error'));  // 立即 rejected

4.5 错误处理

javascript
// ❌ 错误: then 的第二个参数无法捕获 then 回调中的错误
promise.then(
    result => { throw new Error('oops'); },
    error => { /* 捕获不到上面的错误 */ }
);

// ✅ 正确: 使用 catch
promise
    .then(result => { throw new Error('oops'); })
    .catch(error => console.error(error));  // 可以捕获

// 未处理的 rejection
window.addEventListener('unhandledrejection', event => {
    console.error('Unhandled:', event.reason);
    event.preventDefault();
});

4.6 手写 Promise

javascript
class MyPromise {
    static PENDING = 'pending';
    static FULFILLED = 'fulfilled';
    static REJECTED = 'rejected';

    constructor(executor) {
        this.status = MyPromise.PENDING;
        this.value = undefined;
        this.reason = undefined;
        this.onFulfilledCallbacks = [];
        this.onRejectedCallbacks = [];

        const resolve = (value) => {
            if (this.status === MyPromise.PENDING) {
                this.status = MyPromise.FULFILLED;
                this.value = value;
                this.onFulfilledCallbacks.forEach(fn => fn());
            }
        };

        const reject = (reason) => {
            if (this.status === MyPromise.PENDING) {
                this.status = MyPromise.REJECTED;
                this.reason = reason;
                this.onRejectedCallbacks.forEach(fn => fn());
            }
        };

        try {
            executor(resolve, reject);
        } catch (error) {
            reject(error);
        }
    }

    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
        onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e; };

        const promise2 = new MyPromise((resolve, reject) => {
            const handleFulfilled = () => {
                queueMicrotask(() => {
                    try {
                        const x = onFulfilled(this.value);
                        this.resolvePromise(promise2, x, resolve, reject);
                    } catch (error) {
                        reject(error);
                    }
                });
            };

            const handleRejected = () => {
                queueMicrotask(() => {
                    try {
                        const x = onRejected(this.reason);
                        this.resolvePromise(promise2, x, resolve, reject);
                    } catch (error) {
                        reject(error);
                    }
                });
            };

            if (this.status === MyPromise.FULFILLED) {
                handleFulfilled();
            } else if (this.status === MyPromise.REJECTED) {
                handleRejected();
            } else {
                this.onFulfilledCallbacks.push(handleFulfilled);
                this.onRejectedCallbacks.push(handleRejected);
            }
        });

        return promise2;
    }

    resolvePromise(promise2, x, resolve, reject) {
        if (promise2 === x) {
            return reject(new TypeError('Chaining cycle detected'));
        }
        if (x instanceof MyPromise) {
            x.then(resolve, reject);
        } else {
            resolve(x);
        }
    }

    catch(onRejected) {
        return this.then(null, onRejected);
    }

    finally(callback) {
        return this.then(
            value => MyPromise.resolve(callback()).then(() => value),
            reason => MyPromise.resolve(callback()).then(() => { throw reason; })
        );
    }

    static resolve(value) {
        if (value instanceof MyPromise) return value;
        return new MyPromise(resolve => resolve(value));
    }

    static reject(reason) {
        return new MyPromise((_, reject) => reject(reason));
    }

    static all(promises) {
        return new MyPromise((resolve, reject) => {
            const results = [];
            let count = 0;
            
            promises.forEach((promise, index) => {
                MyPromise.resolve(promise).then(
                    value => {
                        results[index] = value;
                        if (++count === promises.length) {
                            resolve(results);
                        }
                    },
                    reject
                );
            });
        });
    }

    static race(promises) {
        return new MyPromise((resolve, reject) => {
            promises.forEach(promise => {
                MyPromise.resolve(promise).then(resolve, reject);
            });
        });
    }
}

5. Generator (生成器)

5.1 基本语法

javascript
function* generator() {
    console.log('start');
    const a = yield 1;
    console.log('a:', a);
    const b = yield 2;
    console.log('b:', b);
    return 3;
}

const gen = generator();

console.log(gen.next());     // start → { value: 1, done: false }
console.log(gen.next('A'));  // a: A  → { value: 2, done: false }
console.log(gen.next('B'));  // b: B  → { value: 3, done: true }
console.log(gen.next());     //       → { value: undefined, done: true }

5.2 Generator 实现异步

javascript
function* fetchUser() {
    const user = yield fetch('/api/user');
    const orders = yield fetch(`/api/orders/${user.id}`);
    return orders;
}

// 手动执行器
function run(generator) {
    const gen = generator();

    function step(value) {
        const result = gen.next(value);
        if (result.done) {
            return Promise.resolve(result.value);
        }
        return Promise.resolve(result.value)
            .then(res => res.json())
            .then(data => step(data));
    }

    return step();
}

run(fetchUser).then(orders => console.log(orders));

5.3 co 库 (自动执行器)

javascript
const co = require('co');

co(function* () {
    const user = yield fetch('/api/user').then(r => r.json());
    const orders = yield fetch(`/api/orders/${user.id}`).then(r => r.json());
    return orders;
}).then(orders => console.log(orders));

6. Async/Await 🔥

6.1 基本语法

javascript
// async 函数总是返回 Promise
async function fetchData() {
    // await 暂停执行, 等待 Promise resolve
    const response = await fetch('/api/data');
    const data = await response.json();
    return data;
}

// 等价于
function fetchData() {
    return fetch('/api/data')
        .then(response => response.json());
}

6.2 错误处理

javascript
// 方式1: try...catch
async function fetchData() {
    try {
        const response = await fetch('/api/data');
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}`);
        }
        return await response.json();
    } catch (error) {
        console.error('Error:', error);
        throw error;  // 重新抛出或返回默认值
    }
}

// 方式2: 包装函数
function to(promise) {
    return promise
        .then(data => [null, data])
        .catch(err => [err, null]);
}

async function fetchData() {
    const [err, response] = await to(fetch('/api/data'));
    if (err) return console.error(err);
    
    const [err2, data] = await to(response.json());
    if (err2) return console.error(err2);
    
    return data;
}

6.3 并行执行

javascript
// ❌ 串行执行 (慢)
async function serial() {
    const user = await fetchUser();      // 等待 1 秒
    const orders = await fetchOrders();  // 再等待 1 秒
    // 总共 2 秒
}

// ✅ 并行执行 (快)
async function parallel() {
    const [user, orders] = await Promise.all([
        fetchUser(),
        fetchOrders()
    ]);
    // 总共 1 秒 (取决于最慢的那个)
}

// 并行发起, 按序等待
async function parallelStart() {
    const userPromise = fetchUser();
    const ordersPromise = fetchOrders();
    
    const user = await userPromise;
    const orders = await ordersPromise;
}

6.4 循环中的 async/await

javascript
const urls = ['/api/1', '/api/2', '/api/3'];

// ❌ forEach 无法等待
urls.forEach(async url => {
    const data = await fetch(url);  // 并行执行, 但无法等待全部完成
});

// ✅ for...of 串行执行
async function serial() {
    for (const url of urls) {
        const data = await fetch(url);  // 一个完成再执行下一个
        console.log(data);
    }
}

// ✅ Promise.all 并行执行
async function parallel() {
    const results = await Promise.all(
        urls.map(url => fetch(url))
    );
    console.log(results);
}

// ✅ for await...of (异步迭代)
async function* asyncGenerator() {
    for (const url of urls) {
        yield fetch(url);
    }
}

for await (const response of asyncGenerator()) {
    console.log(response);
}

6.5 顶层 await (ES2022)

javascript
// 在模块顶层直接使用 await
// file.mjs
const data = await fetch('/api/config').then(r => r.json());
export { data };

// 注意: 仅在 ES Module 中可用

7. 异步控制流

7.1 并发限制

javascript
async function asyncPool(limit, items, iteratorFn) {
    const results = [];
    const executing = [];

    for (const [index, item] of items.entries()) {
        const p = Promise.resolve().then(() => iteratorFn(item, index));
        results.push(p);

        if (limit <= items.length) {
            const e = p.then(() => executing.splice(executing.indexOf(e), 1));
            executing.push(e);

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

    return Promise.all(results);
}

// 使用: 最多同时 3 个请求
const urls = [url1, url2, url3, url4, url5, url6, url7, url8, url9, url10];
await asyncPool(3, urls, async (url) => {
    const res = await fetch(url);
    return res.json();
});

7.2 重试机制

javascript
async function retry(fn, retries = 3, delay = 1000) {
    for (let i = 0; i < retries; i++) {
        try {
            return await fn();
        } catch (error) {
            if (i === retries - 1) throw error;
            
            console.log(`Retry ${i + 1}/${retries}...`);
            await new Promise(r => setTimeout(r, delay * Math.pow(2, i)));  // 指数退避
        }
    }
}

// 使用
const data = await retry(() => fetch('/api/unstable'), 3, 1000);

7.3 超时控制

javascript
function withTimeout(promise, ms) {
    const timeout = new Promise((_, reject) => {
        setTimeout(() => reject(new Error('Timeout')), ms);
    });
    return Promise.race([promise, timeout]);
}

// 使用
try {
    const data = await withTimeout(fetch('/api/slow'), 5000);
} catch (error) {
    if (error.message === 'Timeout') {
        console.log('Request timed out');
    }
}

// 现代方式: AbortSignal.timeout
const response = await fetch('/api/slow', {
    signal: AbortSignal.timeout(5000)
});

7.4 取消异步操作

javascript
// AbortController
const controller = new AbortController();

fetch('/api/data', { signal: controller.signal })
    .then(response => response.json())
    .catch(error => {
        if (error.name === 'AbortError') {
            console.log('Fetch aborted');
        }
    });

// 取消
controller.abort();

// 可取消的 Promise 包装
function cancellable(executor) {
    let cancel;
    const promise = new Promise((resolve, reject) => {
        cancel = () => reject(new Error('Cancelled'));
        executor(resolve, reject);
    });
    return { promise, cancel };
}

7.5 防抖与节流

javascript
// 防抖: 延迟执行, 期间有新调用则重新计时
function debounce(fn, delay) {
    let timer;
    return function (...args) {
        clearTimeout(timer);
        timer = setTimeout(() => fn.apply(this, args), delay);
    };
}

// 异步防抖
function asyncDebounce(fn, delay) {
    let timer;
    let resolveList = [];

    return function (...args) {
        return new Promise((resolve) => {
            clearTimeout(timer);
            resolveList.push(resolve);

            timer = setTimeout(async () => {
                const result = await fn.apply(this, args);
                resolveList.forEach(r => r(result));
                resolveList = [];
            }, delay);
        });
    };
}

// 节流: 固定间隔执行
function throttle(fn, interval) {
    let lastTime = 0;
    return function (...args) {
        const now = Date.now();
        if (now - lastTime >= interval) {
            lastTime = now;
            return fn.apply(this, args);
        }
    };
}

8. 面试高频问题

Q1: 宏任务和微任务的区别?

维度宏任务微任务
代表setTimeout, I/OPromise.then, queueMicrotask
执行时机每次取一个执行清空所有微任务后再取宏任务
优先级

Q2: Promise.all 和 Promise.allSettled 的区别?

  • Promise.all: 全部成功才成功,任一失败则立即失败
  • Promise.allSettled: 等待全部完成,返回每个结果的状态

Q3: async/await 的本质是什么?

async/await 是 Generator 的语法糖,async 函数返回 Promise,await 相当于 yield,自动执行器处理 next() 调用。

Q4: 如何处理多个 async 操作的并行执行?

使用 Promise.all:

javascript
const [a, b, c] = await Promise.all([fnA(), fnB(), fnC()]);

Q5: setTimeout 的最小延迟是多少?

  • 浏览器: 嵌套超过 5 层后最小 4ms
  • Node.js: 最小 1ms
  • HTML5 规范: 最小 4ms

Q6: process.nextTick 和 Promise.then 哪个先执行?

Node.js 中 process.nextTick 优先级高于 Promise.then 微任务。

javascript
Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));
// 输出: nextTick, promise

Q7: 如何取消一个 Promise?

Promise 本身不可取消,但可以:

  1. 使用 AbortController (fetch)
  2. 使用取消令牌模式
  3. 使用 Promise.race 与超时 Promise

Q8: async 函数中 return 和 return await 有区别吗?

在 try...catch 中有区别:

javascript
async function foo() {
    try {
        return Promise.reject('error');  // 不会被 catch
        // return await Promise.reject('error');  // 会被 catch
    } catch (e) {
        console.log('caught:', e);
    }
}

Q9: 如何实现并发限制?

使用 asyncPool 模式:

  1. 维护正在执行的 Promise 数组
  2. 当达到限制时使用 Promise.race 等待
  3. 完成一个后添加下一个

Q10: Event Loop 在浏览器和 Node.js 中有什么区别?

维度浏览器Node.js
微任务时机每个宏任务后执行每个阶段切换时执行
特有 APIrequestAnimationFramesetImmediate, process.nextTick
阶段简单循环6 个阶段

前端面试知识库