源本科技 | 码上会

JavaScript Promise

2026/03/13
11
0

学习目标

  • 理解 Promise 的三种状态(Pending, Fulfilled, Rejected)及其生命周期。

  • 掌握 Promise 的核心语法,包括 new Promise.then().catch().finally()

  • 熟练运用高级组合方法(all, allSettled, race, any)处理复杂的并发异步任务。

  • 学会利用 Promise 解决“回调地狱”问题,并实现超时控制和串行 / 并行混合执行模式。


Promise

什么是 Promise

JavaScript Promise 是处理异步操作(如 API 调用、文件加载、定时延迟)的现代标准。你可以将 Promise 想象成一个未来值的占位符。它代表一个现在可能未完成,但将来一定会完成(或失败)的操作。

三种状态

Promise 对象在其生命周期中处于以下三种状态之一:

  1. Pending (待定): 初始状态,操作尚未完成,既未成功也未失败。

  2. Fulfilled (已兑现): 操作成功完成,Promise 拥有一个确定的结果值。

  3. Rejected (已拒绝): 操作失败,Promise 拥有一个表示错误原因的变量。

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

基础示例

判断奇偶数

// 创建一个 Promise
let checkEven = new Promise((resolve, reject) => {
    let number = 4;
    
    // 模拟逻辑判断
    if (number % 2 === 0) {
        resolve("这个数字是偶数!"); // 成功
    } else {
        reject("这个数字是奇数!");   // 失败
    }
});

// 处理结果
checkEven
    .then((message) => console.log(message)) // 成功时执行
    .catch((error) => console.error(error)); // 失败时执行

关键点说明:

  • resolvereject 只是参数名,并非 JavaScript 关键字。你可以将它们命名为 successfailure,但为了代码可读性,建议遵循社区惯例使用 resolvereject

  • .then() 接收成功后的回调函数。

  • .catch() 接收失败后的回调函数。

标准语法

let promise = new Promise((resolve, reject) => {
    // 执行异步操作 (例如:网络请求、定时器)
    
    if (/* 操作成功 */) {
        resolve("任务成功完成"); 
    } else {
        reject("任务失败,发生错误");
    }
});
  • resolve(value): 将 Promise 状态标记为 Fulfilled,并传递结果值。

  • reject(error): 将 Promise 状态标记为 Rejected,并传递错误对象或信息。


高级方法与模式

为了更高效地处理复杂的异步场景,JavaScript 提供了一系列强大的静态方法和模式。

并发控制

Promise.all()

等待所有 Promise 成功完成。如果其中任何一个失败,整个 Promise.all 会立即拒绝。

  • 适用场景: 需要同时获取多个数据,且所有数据都必须成功才能继续后续逻辑(如加载页面的多个核心组件)。

Promise.all([
    Promise.resolve("任务 1 完成"),
    Promise.resolve("任务 2 完成"),
    Promise.reject("任务 3 失败") // 只要有一个失败,整体即失败
])
.then((results) => console.log("所有结果:", results))
.catch((error) => console.error("发生错误:", error));
// 输出: 发生错误:任务 3 失败

全量结算

Promise.allSettled()

等待所有 Promise 结束(无论成功或失败),返回一个包含每个 Promise 结果的数组。

  • 适用场景: 需要知道每个任务的状态,即使部分失败也要处理其他成功的结果(如批量上传文件,统计成功和失败的数量)。

Promise.allSettled([
    Promise.resolve("任务 1 完成"),
    Promise.reject("任务 2 失败"),
    Promise.resolve("任务 3 完成")
])
.then((results) => console.log(results));
/* 输出示例:
[
  { status: 'fulfilled', value: '任务 1 完成' },
  { status: 'rejected', reason: '任务 2 失败' },
  { status: 'fulfilled', value: '任务 3 完成' }
]
*/

竞速模式

Promise.race()

返回第一个settled(成功或失败)的 Promise 的结果。一旦有一个完成,其他的将被忽略。

  • 适用场景: 多个备用源获取数据,谁快用谁;或者实现超时机制。

Promise.race([
    new Promise((resolve) =>
        setTimeout(() => resolve("任务 1 完成"), 1000)),
    new Promise((resolve) =>
        setTimeout(() => resolve("任务 2 完成"), 500)) // 这个更快
])
.then((result) => console.log(result)); 
// 输出: 任务 2 完成

首个成功

Promise.any()

返回第一个成功的 Promise。只有当所有 Promise 都失败时,它才会拒绝,并抛出一个 AggregateError

  • 适用场景: 尝试多个镜像服务器下载资源,只要有一个成功即可。

Promise.any([
    Promise.reject("任务 1 失败"),
    Promise.resolve("任务 2 成功"), // 第一个成功的
    Promise.resolve("任务 3 成功")
])
.then((result) => console.log(result))
.catch((error) => console.error(error));
// 输出: 任务 2 成功

快速创建状态

  • Promise.resolve(value): 直接返回一个已成功的 Promise。常用于将非 Promise 值标准化为 Promise。

  • Promise.reject(reason): 直接返回一个已失败的 Promise。

Promise.resolve("立即成功")
    .then((value) => console.log(value));

Promise.reject("立即失败")
    .catch((error) => console.error(error));

最终清理

Promise.finally()

无论 Promise 成功还是失败,都会执行的代码块。常用于清理工作(如隐藏 Loading 动画、关闭数据库连接)。

Promise.resolve("任务完成")
    .then((result) => console.log(result))
    .catch((error) => console.error(error))
    .finally(() => console.log("清理工作已完成"));
// 输出顺序: 任务完成 -> 清理工作已完成

高级模式

链式调用

通过 .then() 方法串联多个异步操作,前一个 .then() 的返回值会自动传递给下一个 .then()

Promise.resolve(5)
    .then((value) => value * 2)      // 返回 10
    .then((value) => value + 3)      // 返回 13
    .then((finalValue) => console.log(finalValue)); 
// 输出: 13

串行执行

如果需要按顺序执行一系列异步任务(后一个依赖前一个的结果),可以使用 Array.prototype.reduce()

let tasks = [1, 2, 3];

tasks.reduce((prevPromise, current) => {
    return prevPromise.then(() => {
        return new Promise((resolve) => {
            console.log(`正在处理任务 ${current}`);
            setTimeout(resolve, 500); // 模拟异步耗时
        });
    });
}, Promise.resolve()); // 初始值为一个已解决的 Promise

// 输出顺序: 任务 1 -> 任务 2 -> 任务 3 (每隔 0.5 秒)

动态创建

根据运行时条件动态创建和解决 Promise。

function asyncTask(taskName) {
    return new Promise((resolve) => {
        setTimeout(() => 
            resolve(`${taskName} 已完成`), 1000);
    });
}

asyncTask("下载文件").then((result) => 
    console.log(result));

超时控制

结合 Promise.race() 实现异步操作的超时中断。

// 模拟一个耗时 3 秒的数据获取
let fetchData = new Promise((resolve) =>
    setTimeout(() => resolve("数据加载成功"), 3000));

// 创建一个 2 秒后拒绝的超时 Promise
let timeout = new Promise((_, reject) =>
    setTimeout(() => reject("请求超时!"), 2000));

// 竞赛:谁先完成就用谁的结果
Promise.race([fetchData, timeout])
    .then((result) => console.log(result))
    .catch((error) => console.error(error));
// 输出: 请求超时! (因为 2s < 3s)

混合执行模式

先并行执行一组任务,待它们全部完成后,再串行执行后续任务。

Promise.all([
    new Promise((resolve) =>
        setTimeout(() => resolve("任务 A 完成"), 1000)),
    new Promise((resolve) =>
        setTimeout(() => resolve("任务 B 完成"), 500))
])
.then(([resultA, resultB]) => {
    console.log("并行结果:", resultA, resultB);
    // 并行结束后,开始新的串行任务
    return new Promise((resolve) =>
        setTimeout(() => resolve("最终任务完成"), 700));
})
.then((finalResult) =>
    console.log(finalResult));

回调转

将传统的基于回调的函数转换为基于 Promise 的函数,以便使用 .then() 链或 await

// 传统回调风格函数
function loadData(callback) {
    setTimeout(() => callback("数据已加载"), 1000);
}

// 封装为 Promise
function promisifiedLoadData() {
    return new Promise((resolve) => {
        loadData((result) => resolve(result));
    });
}

// 使用 Promise 风格调用
promisifiedLoadData().then((data) => 
    console.log(data));

核心优势

特性

回调函数

Promise

代码结构

容易形成深层嵌套 (回调地狱)

扁平化,链式调用,结构清晰

错误处理

需在每个层级单独处理,易遗漏

统一的 .catch() 捕获链上所有错误

执行流程

难以控制执行顺序

轻松实现串行 (.then) 或并行 (Promise.all)

状态管理

无明确状态,依赖人工逻辑

内置 Pending/Fulfilled/Rejected 状态机

  • 避免回调地狱: Promise 将嵌套的回调转化为线性的链式调用,极大提升了代码可读性。

  • 统一错误处理: 错误可以在链条末尾一次性捕获,简化了异常管理逻辑。

  • 灵活的组合能力: 通过 all, race 等方法,可以优雅地处理复杂的并发和竞争场景。


总结

  1. 状态机: 理解 Promise 的三种状态及其不可逆性是基础。

  2. 链式调用: 利用 .then() 传递数据,利用 .catch() 统一捕获错误。

  3. 组合方法:

    • 需全部成功:用 Promise.all

    • 需全部结果(不管成败):用 Promise.allSettled

    • 需最快结果:用 Promise.race

    • 需首个成功:用 Promise.any

  4. 实用模式: 掌握超时控制和回调转 Promise 的技巧,能解决大量实际工程问题。