在单线程的JavaScript宇宙中,异步编程不是可选项,而是生存法则。它让我们能够处理I/O操作、网络请求和用户交互,而不会阻塞主线程。但这条进化之路,充满了挑战与智慧。

一、历史的回响:从“回调地狱”到Promise的曙光

1.1 回调函数:混乱的开端
最初,我们只有回调。它简单,却极易导致著名的“回调地狱”。

javascript

复制

下载

getData(function(a) {
    getMoreData(a, function(b) {
        getMoreData(b, function(c) {
            getMoreData(c, function(d) {
                // ... 无尽的嵌套
                console.log('Callback Hell!');
            });
        });
    });
});

痛点: 嵌套过深、错误处理困难(“金字塔厄运”)、代码难以阅读和维护。

1.2 Promise:救世主的降临
Promise/A+规范的提出,是JavaScript异步编程的第一个里程碑。它代表了一个尚未完成但未来会产生结果的操作。

核心概念:

  • 状态机: pending(等待)、fulfilled(成功)、rejected(失败)。状态一旦改变,不可逆。

  • 链式调用: .then() 和 .catch() 方法返回一个新的Promise,从而实现了链式调用,解决了回调地狱。

javascript

复制

下载

getData()
    .then(a => getMoreData(a))
    .then(b => getMoreData(b))
    .then(c => getMoreData(c))
    .then(d => {
        console.log('Flat chain!');
    })
    .catch(error => {
        // 统一的错误处理
        console.error('Something went wrong:', error);
    });
二、Promise的深度剖析:不止于“用”,更要于“懂”

2.1 手写一个符合Promise/A+规范的Promise
要真正理解Promise,没有比自己实现一个更好的方式了。以下是核心逻辑的简化实现:

javascript

复制

下载

class MyPromise {
    constructor(executor) {
        this._state = 'pending';
        this._value = undefined;
        this._callbacks = [];

        const resolve = (value) => {
            if (this._state !== 'pending') return;
            this._state = 'fulfilled';
            this._value = value;
            this._callbacks.forEach(cb => this._handle(cb));
        };

        const reject = (reason) => {
            if (this._state !== 'pending') return;
            this._state = 'rejected';
            this._value = reason;
            this._callbacks.forEach(cb => this._handle(cb));
        };

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

    then(onFulfilled, onRejected) {
        return new MyPromise((resolve, reject) => {
            this._callbacks.push({
                onFulfilled,
                onRejected,
                resolve,
                reject
            });
            if (this._state !== 'pending') {
                this._handle({ onFulfilled, onRejected, resolve, reject });
            }
        });
    }

    _handle(callback) {
        const { onFulfilled, onRejected, resolve, reject } = callback;
        const next = () => {
            const cb = this._state === 'fulfilled' ? onFulfilled : onRejected;
            if (typeof cb !== 'function') {
                (this._state === 'fulfilled' ? resolve : reject)(this._value);
                return;
            }
            try {
                const result = cb(this._value);
                if (result instanceof MyPromise) {
                    result.then(resolve, reject);
                } else {
                    resolve(result);
                }
            } catch (err) {
                reject(err);
            }
        };
        // 使用setTimeout模拟微任务,实际中由JS引擎微任务队列处理
        setTimeout(next, 0);
    }

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

// 使用示例
new MyPromise((resolve) => resolve('Hello'))
    .then(val => val + ' World')
    .then(console.log); // 输出 "Hello World"

通过手写,你深刻理解了:

  • 状态不可变的原因

  • .then 方法返回新Promise的实现

  • 异步执行(微任务)的机制

  • 值穿透的原理

2.2 Promise的进阶API与模式

  • Promise.all: 等待所有成功,或任何一个失败。

  • Promise.allSettled: 等待所有Promise完成(无论成功失败)。

  • Promise.race: 取最先完成的Promise(成功或失败)。

  • Promise.any: 取最先成功的Promise。

三、Async/Await:同步风格书写异步代码的终极语法糖

Async/Await是基于Generator和Promise的语法糖,它让异步代码看起来和同步代码一样。

3.1 基本用法

javascript

复制

下载

// 基于Promise的旧方式
function fetchUserData(userId) {
    return fetch(`/api/users/${userId}`)
        .then(response => response.json())
        .then(user => fetch(`/api/posts/${user.id}`))
        .then(posts => ({ user, posts }));
}

// 使用Async/Await的新方式
async function fetchUserData(userId) {
    const response = await fetch(`/api/users/${userId}`);
    const user = await response.json();
    const posts = await fetch(`/api/posts/${user.id}`);
    return { user, posts };
}

3.2 错误处理的演进

javascript

复制

下载

// 旧方式:.catch()
fetchUserData(1)
    .then(data => console.log(data))
    .catch(err => console.error('Fetch failed:', err));

// 新方式:try...catch
async function main() {
    try {
        const data = await fetchUserData(1);
        console.log(data);
    } catch (err) {
        console.error('Fetch failed:', err);
    }
}
main();

3.3 底层原理揭秘
Async函数本质上是一个返回Promise的Generator函数的语法糖。

javascript

复制

下载

// 模拟Async函数的执行器
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
    try {
        var info = gen[key](arg);
        var value = info.value;
    } catch (error) {
        reject(error);
        return;
    }
    if (info.done) {
        resolve(value);
    } else {
        Promise.resolve(value).then(_next, _throw);
    }
}

// 模拟Async函数的实现
function _asyncToGenerator(fn) {
    return function () {
        const self = this, args = arguments;
        return new Promise((resolve, reject) => {
            const gen = fn.apply(self, args);
            function _next(value) {
                asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
            }
            function _throw(err) {
                asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
            }
            _next(undefined);
        });
    };
}

// 使用
const fetchUserData = _asyncToGenerator(function* (userId) {
    const response = yield fetch(`/api/users/${userId}`);
    const user = yield response.json();
    const posts = yield fetch(`/api/posts/${user.id}`);
    return { user, posts };
});
四、巅峰之战:高级并发模式与性能优化

当你有成百上千个异步任务需要处理时,简单的Promise.all可能会导致内存溢出或服务器被击垮。这时,你需要并发控制。

4.1 手写一个高性能的并发控制器

javascript

复制

下载

class AsyncPool {
    constructor(maxConcurrency) {
        this.maxConcurrency = maxConcurrency;
        this.running = 0;
        this.queue = [];
    }

    add(task) {
        return new Promise((resolve, reject) => {
            this.queue.push(() => task().then(resolve, reject));
            this._run();
        });
    }

    _run() {
        // 如果队列已空或并发数已达上限,则返回
        if (this.queue.length === 0 || this.running >= this.maxConcurrency) {
            return;
        }

        this.running++;
        const task = this.queue.shift();

        task()
            .finally(() => {
                this.running--;
                this._run(); // 一个任务完成,立即执行下一个
            });
    }
}

// 使用示例:控制最多同时3个请求
const pool = new AsyncPool(3);
const urls = [...Array(10).keys()].map(i => `https://jsonplaceholder.typicode.com/posts/${i + 1}`);

const results = await Promise.all(
    urls.map(url => 
        pool.add(() => 
            fetch(url).then(res => res.json())
        )
    )
);
console.log('All requests completed with concurrency control:', results);

4.2 更优雅的解决方案:使用p-limit
在实际项目中,推荐使用成熟的库。

bash

复制

下载

npm install p-limit

javascript

复制

下载

import pLimit from 'p-limit';

const limit = pLimit(3); // 最大并发数为3

const promises = urls.map(url => 
    limit(() => 
        fetch(url).then(res => res.json())
    )
);

const results = await Promise.all(promises);
五、最佳实践与性能陷阱
  1. 永远不要忘记Await: 在async函数中调用另一个async函数,除非你明确想要"fire and forget",否则不要忘记await。

  2. 并行 vs 串行: 无依赖的异步任务使用Promise.all并行执行;有依赖的需要串行执行。

  3. 错误处理的边界: 在Async函数内部使用try-catch,在外部也要有适当的错误处理机制。

  4. 避免不必要的Await: 如果Promise不需要等待其结果,直接返回它,而不是await它。

  5. 理解微任务队列: Promise回调在微任务队列中执行,这会影响代码的执行顺序。

javascript

复制

下载

// 示例:执行顺序
console.log('script start');

setTimeout(() => console.log('setTimeout'), 0);

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

console.log('script end');

// 输出顺序:
// script start → script end → promise1 → promise2 → setTimeout
六、总结

JavaScript的异步编程之旅,是一场从混沌到秩序的进化:

  • 回调函数 让我们起步,但陷入了地狱。

  • Promise 为我们带来了结构和可组合性。

  • Async/Await 用同步的写法实现了异步的逻辑,极大地提升了可读性。

  • 并发控制 则是我们在大规模异步处理中的终极武器。

理解每一层背后的原理,不仅能让你写出更健壮、高效的代码,更能让你在面对复杂异步场景时游刃有余。异步编程不再是JavaScript的痛点,而是它最强大的特性之一。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐