JavaScript 异步编程:Promise 与 async/await

小飞兽 JavaScript 149 次阅读 2026-06-16

JavaScript 异步编程:Promise 与 async/await

一、Introduction(简介)

JavaScript 是单线程语言,同一时刻只能执行一个任务。异步编程是 JavaScript 处理耗时操作(网络请求、文件读写、定时器)的核心机制,它避免了阻塞主线程,让程序在等待 I/O 完成期间继续执行其他任务。

在 ES6 之前,异步编程主要依赖回调函数(Callback),但回调地狱(Callback Hell)使代码难以阅读和维护。ES6 引入的 Promise 提供了更清晰的异步流程控制,而 ES2017 的 async/await 则进一步将异步代码写成同步风格,大幅提升了代码可读性。

本篇文章系统讲解 JavaScript 异步编程的演进历程、Promise 的原理与用法、async/await 语法、以及常见的异步流程控制模式(串行、并行、竞速等),帮助读者彻底掌握现代 JavaScript 异步编程。

二、基础语法

2.1 回调函数(Callback)— 异步的起点

回调是最原始的异步处理方式,将函数作为参数传入,在异步操作完成后调用。

// 传统的回调函数示例:读取文件(Node.js 风格)
const fs = require('fs');

fs.readFile('./data.txt', 'utf8', function(err, data) {
if (err) {
console.error('读取失败:', err);
return;
}
console.log('文件内容:', data);
});

// 回调地狱(嵌套过深,难以维护)
fs.readFile('./a.txt', 'utf8', function(err, dataA) {
if (err) return console.error(err);
fs.readFile('./b.txt', 'utf8', function(err, dataB) {
if (err) return console.error(err);
fs.readFile('./c.txt', 'utf8', function(err, dataC) {
if (err) return console.error(err);
console.log(dataA, dataB, dataC);
});
});
});

2.2 Promise 的基本概念

Promise 是一个对象,代表一个异步操作的最终结果(成功值或失败原因)。Promise 有三种状态:

    • Pending(待定):初始状态,操作进行中。
    • Fulfilled(已兑现):操作成功,Promise 有了最终结果值。
    • Rejected(已拒绝):操作失败,Promise 有了失败原因。

    Promise 状态一旦改变(从 Pending 变为 Fulfilled 或 Rejected),就不可再变。

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

    // 消费 Promise
    promise
    .then(result => {
    console.log('Fulfilled:', result);
    })
    .catch(error => {
    console.error('Rejected:', error.message);
    })
    .finally(() => {
    console.log('无论成功或失败,都会执行');
    });

    2.3 Promise 链式调用

    Promise 的 .then() 方法返回一个新的 Promise,支持链式调用,是解决回调地狱的关键。

    // 链式调用:每个 .then() 返回的值会传递给下一个 .then()
    fetchUserData()
        .then(user => fetchUserPosts(user.id))     // 接收上一步的结果
        .then(posts => filterPublishedPosts(posts))
        .then(published => sortByDate(published))
        .then(sorted => renderPosts(sorted))
        .catch(err => console.error('出错了:', err));
    

    // 模拟上述函数
    function fetchUserData() {
    return Promise.resolve({ id: 1, name: 'Alice' });
    }
    function fetchUserPosts(userId) {
    return Promise.resolve([
    { id: 101, title: 'Post A', published: true },
    { id: 102, title: 'Post B', published: false },
    ]);
    }
    function filterPublishedPosts(posts) {
    return Promise.resolve(posts.filter(p => p.published));
    }
    function sortByDate(posts) {
    return Promise.resolve(posts.reverse());
    }
    function renderPosts(posts) {
    console.log('渲染文章:', posts);
    return posts;
    }

    2.4 Promise.all — 并行执行多个异步任务

    当多个异步任务相互独立,可以并行执行时,使用 Promise.all 等待所有任务完成。

    // 三个独立的网络请求,并行执行
    const fetchUser = () => fetch('/api/user/1').then(r => r.json());
    const fetchPosts = () => fetch('/api/posts').then(r => r.json());
    const fetchComments = () => fetch('/api/comments').then(r => r.json());
    

    // Promise.all:全部成功才进入 then,有一个失败就进入 catch
    Promise.all([fetchUser(), fetchPosts(), fetchComments()])
    .then(([user, posts, comments]) => {
    console.log('用户:', user);
    console.log('文章:', posts);
    console.log('评论:', comments);
    return { user, posts, comments };
    })
    .catch(err => {
    console.error('某个请求失败了:', err);
    });

    // Promise.allSettled:无论成功失败都返回结果(ES2020)
    Promise.allSettled([fetchUser(), fetchPosts(), fetchComments()])
    .then(results => {
    results.forEach((result, i) => {
    if (result.status === 'fulfilled') {
    console.log(${i} 成功:, result.value);
    } else {
    console.log(${i} 失败:, result.reason);
    }
    });
    });

    2.5 Promise.race 与 Promise.any

    // Promise.race:返回最先完成(成功或失败)的 Promise 的结果
    Promise.race([
        fetch('/api/fast'),
        new Promise((_, reject) => setTimeout(() => reject(new Error('超时')), 5000))
    ]).then(result => console.log(result))
      .catch(err => console.error('超时或出错:', err.message));
    

    // Promise.any:返回最先成功完成的 Promise 的结果,忽略失败(ES2021)
    // 适用于:多个 CDN 同时请求,用最快响应的那个
    Promise.any([
    fetch('https://cdn1.example.com/resource'),
    fetch('https://cdn2.example.com/resource'),
    fetch('https://cdn3.example.com/resource'),
    ]).then(result => console.log('最快响应:', result))
    .catch(err => console.error('所有 CDN 都失败了:', err.errors));

    2.6 async/await 语法

    async/await 是 ES2017 引入的语法糖,让异步代码读起来像同步代码。async 函数自动返回 Promise;await 等待 Promise resolve,并获取其结果值。

    // async 函数声明
    async function fetchUserInfo(userId) {
        // await 暂停函数执行,等待 Promise 完成
        const user = await fetch(/api/user/${userId}).then(r => r.json());
        const posts = await fetch(/api/posts?userId=${userId}).then(r => r.json());
        return { user, posts };
    }
    

    // async 函数也可以用箭头函数
    const getData = async (id) => {
    const data = await fetchData(id);
    return data;
    };

    // 调用 async 函数(返回 Promise)
    fetchUserInfo(1).then(({ user, posts }) => {
    console.log(user, posts);
    });

    // 配合 try/catch 处理错误
    async function loadContent() {
    try {
    const user = await fetchUser(1);
    const posts = await fetchPosts(user.id);
    console.log(posts);
    } catch (err) {
    console.error('加载失败:', err);
    }
    }

    2.7 并行优化:同时 await 多个 Promise

    // 串行:逐个等待(总耗时 = a + b + c)
    async function serial() {
        const a = await taskA(); // 1秒
        const b = await taskB(); // 1秒
        const c = await taskC(); // 1秒
        return [a, b, c];
    }
    

    // 并行:同时启动(总耗时 ≈ max(a, b, c))
    async function parallel() {
    // 同时发起三个任务,不等待
    const [a, b, c] = await Promise.all([taskA(), taskB(), taskTaskC()]);
    return [a, b, c];
    }

    // 示例:三个耗时1秒的任务
    const taskA = () => new Promise(r => setTimeout(() => { console.log('A完成'); r('A'); }, 1000));
    const taskB = () => new Promise(r => setTimeout(() => { console.log('B完成'); r('B'); }, 1000));
    const taskC = () => new Promise(r => setTimeout(() => { console.log('C完成'); r('C'); }, 1000));

    // 测试
    console.time('serial');
    serial().then(() => console.timeEnd('serial')); // 约3秒

    console.time('parallel');
    parallel().then(() => console.timeEnd('parallel')); // 约1秒

    2.8 async/await 错误处理

    // 方法1:try/catch(推荐,最接近同步语法)
    async function method1() {
        try {
            const data = await apiCall();
            return data;
        } catch (err) {
            console.error('请求失败:', err.message);
            throw err; // 重新抛出,外层仍可捕获
        }
    }
    

    // 方法2:.catch() 捕获
    async function method2() {
    const data = await apiCall().catch(err => {
    console.error('请求失败:', err.message);
    return null; // 返回默认值
    });
    return data;
    }

    // 方法3:使用 Promise.allSettled 批量处理,不轻易抛出错误
    async function method3() {
    const results = await Promise.allSettled([
    apiCall('url1'),
    apiCall('url2'),
    apiCall('url3'),
    ]);

    results.forEach((result, i) => {
    if (result.status === 'fulfilled') {
    console.log(${i} 成功:, result.value);
    } else {
    console.warn(${i} 失败:, result.reason.message);
    }
    });
    }

    三、代码示例

    3.1 完整的 async/await 增删改查示例

    // 模拟 API 延迟
    const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
    const mockApi = {
        async getUser(id) {
            await delay(300);
            return { id, name: 'Alice', email: 'alice@example.com' };
        },
        async getPosts(userId) {
            await delay(500);
            return [
                { id: 1, title: 'ES6 新特性', views: 100 },
                { id: 2, title: 'Promise 详解', views: 200 },
            ];
        },
        async createPost(userId, title) {
            await delay(400);
            return { id: Math.random(), userId, title, createdAt: new Date() };
        }
    };
    

    // 使用 async/await 组合多个 API 调用
    async function getUserWithPosts(userId) {
    try {
    // 两个独立的请求可以并行
    const [user, posts] = await Promise.all([
    mockApi.getUser(userId),
    mockApi.getPosts(userId)
    ]);
    return { user, posts, totalViews: posts.reduce((s, p) => s + p.views, 0) };
    } catch (err) {
    console.error('加载失败:', err);
    return null;
    }
    }

    // 创建文章后刷新列表
    async function createAndReload(userId, title) {
    const newPost = await mockApi.createPost(userId, title);
    console.log('文章创建成功:', newPost);
    const { posts } = await getUserWithPosts(userId);
    return posts;
    }

    // 测试
    getUserWithPosts(1).then(result => {
    console.log('用户信息:', result.user);
    console.log('文章列表:', result.posts);
    console.log('总浏览量:', result.totalViews);
    });

    3.2 手写简易 Promise 实现

    // 简化版 MyPromise 实现,帮助理解 Promise 原理
    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 => cb.onFulfilled(value));
    };

    const reject = (reason) => {
    if (this.state !== 'pending') return;
    this.state = 'rejected';
    this.value = reason;
    this.callbacks.forEach(cb => cb.onRejected(reason));
    };

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

    then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
    const handle = (callback, fallback) => {
    try {
    const fn = typeof callback === 'function' ? callback : fallback;
    resolve(fn(this.value));
    } catch (err) {
    reject(err);
    }
    };

    if (this.state === 'fulfilled') {
    handle(onFulfilled, v => v);
    } else if (this.state === 'rejected') {
    handle(onRejected, err => { throw err; });
    } else {
    this.callbacks.push({
    onFulfilled: () => handle(onFulfilled, v => v),
    onRejected: () => handle(onRejected, err => { throw err; })
    });
    }
    });
    }

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

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

    // 测试
    new MyPromise((resolve) => setTimeout(() => resolve(42), 1000))
    .then(v => { console.log('得到值:', v); return v * 2; })
    .then(v => console.log('翻倍:', v));

    四、运行效果

    4.1 Node.js 环境运行示例

    $ node async-demo.js
    得到值: 42
    翻倍: 84
    用户信息: { id: 1, name: 'Alice', email: 'alice@example.com' }
    文章列表: [ { id: 1, title: 'ES6 新特性', views: 100 }, ... ]
    总浏览量: 300
    serial: 3004ms   (串行,3个任务各1秒)
    parallel: 1003ms (并行,同时执行,总耗时≈1秒)
    

    4.2 浏览器环境测试

    在浏览器控制台粘贴以下代码,观察执行顺序:

    async function test() {
        console.log('1 - 开始');
        await new Promise(r => setTimeout(r, 1000));
        console.log('2 - 1秒后');
        await new Promise(r => setTimeout(r, 1000));
        console.log('3 - 再过1秒(共2秒)');
    }
    test();
    console.log('4 - 异步函数启动后立即执行');
    

    /* 输出顺序:

  • 1 - 开始

  • 4 - 异步函数启动后立即执行(主线程未被阻塞)

  • 2 - 1秒后

  • 3 - 再过1秒(共2秒)

*/

五、常见问题

Q1:async/await 和 Promise 是什么关系?

答: async/await 是 Promise 的语法糖。async 函数自动返回 Promise,await 等待 Promise resolve。实际上所有 async 函数背后的执行机制都是 Promise。以下两者等价:

// 写法1:纯 Promise + then/catch
function getData1() {
    return fetch('/api/data')
        .then(r => r.json())
        .then(data => process(data));
}

// 写法2:async/await(编译后实际等价于写法1)
async function getData2() {
const r = await fetch('/api/data');
const data = await r.json();
return process(data);
}

Q2:forEach 配合 async/await 能正常工作吗?

答: 不能。forEach 不等待 async 回调,会并行发起所有请求。

// 错误写法:forEach 不会等待 await
async function wrong() {
    const ids = [1, 2, 3];
    const results = [];
    ids.forEach(async id => {
        const data = await fetchData(id); // 不等待,直接继续
        results.push(data);
    });
    console.log(results); // [] (可能为空,任务还在进行中)
}

// 正确写法:使用 for...of(串行)或 Promise.all(并行)
async function correct_serial() {
const ids = [1, 2, 3];
const results = [];
for (const id of ids) {
const data = await fetchData(id); // 等待每个完成后再下一个
results.push(data);
}
return results;
}

async function correct_parallel() {
const ids = [1, 2, 3];
const results = await Promise.all(ids.map(id => fetchData(id)));
return results;
}

Q3:await 在 Promise.all 内部还是外部?

答: 根据需求选择:

    • await 在 Promise.all 外部:等所有任务并行完成后再继续(推荐用于独立任务)。
    • await 在 Promise.all 内部(每个单独 await):实际上退化为串行执行,性能差,应避免。
    // ✅ 推荐:并行,等所有完成后再往下走
    const [a, b, c] = await Promise.all([taskA(), taskB(), taskC()]);
    console.log('所有任务完成');
    

    // ❌ 错误:虽然用了 Promise.all,但 await 写在内部
    const results = await Promise.all([
    await taskA(), // 阻塞等待 taskA 完成才开始 taskB
    await taskB(),
    await taskC(),
    ]);

    Q4:Promise.then/.catch 之后还会继续执行吗?

    答: 会的。Promise 链式调用中,每个 .then() 返回新的 Promise,即使前面的 .then() 已经有值,也不会自动终止链。

    Promise.resolve(1)
        .then(v => {
            console.log('第一个 then:', v); // 1
            return v + 1;
        })
        .then(v => {
            console.log('第二个 then:', v); // 2
            // 不返回,则下一个 then 收到 undefined
        })
        .then(v => {
            console.log('第三个 then:', v); // undefined
        });
    

    // 如果想提前终止 Promise 链,有以下方式:
    // 1. 抛出异常,进入 .catch()
    Promise.resolve(1)
    .then(v => { throw new Error('stop'); })
    .then(v => console.log('不会执行'))
    .catch(err => console.log('在 catch 处理:', err.message));

    // 2. 返回一个永不 resolve 的 Promise
    const never = () => new Promise(() => {});
    Promise.resolve(1)
    .then(never)
    .then(v => console.log('不会执行'));

    Q5:async 函数里 return 值 vs return await 值,有什么区别?

    答: 有微妙但重要的区别:

    // return value:值直接作为 Promise 的 resolve 值
    async function fn1() {
        return 42;
    }
    

    // return await value:多走一步,先 await 再 return
    // 区别在于错误堆栈和调试行为
    async function fn2() {
    return await 42; // await 一个非 Promise 值,几乎没区别
    }

    // 真正有区别的情况:
    async function parent() {
    // return await 可能捕获异常,但 return value 会直接抛出
    try {
    return await riskyOperation(); // 错误在 parent 上下文中抛出
    } catch (e) {
    return 'fallback';
    }
    }

    // 推荐写法:减少不必要的 await,减少微任务开销
    async function getData() {
    const r = await fetch('/api/data');
    return r.json(); // 直接 return,调用方 await
    }

    六、延伸阅读

    1. MDN - Promisehttps://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise — Promise 官方文档,详细介绍了所有实例方法。