Promise状态机与状态流转

本专栏聚焦Promise的核心原理与高级应用,包含: ✓ Promise A+规范深度解读 ✓ 手写实现与源码分析 ✓ 异步编程设计模式 ✓ 性能调优与错误处理
适合有JavaScript基础,希望深入异步编程的开发者。我们将用最少的篇幅,讲透最核心的知识。

引言:Promise状态机

在之前的文章中,我们曾提到Promise三种状态:pending(待定)fulfilled(兑现)rejected(拒绝)。而且还有一个关键特性:

  • 状态不可逆:一旦状态从pending变为fulfilled或rejected,就永远不会再改变。

这是什么意思呢?我们可以举一个简单的例子来帮助大家理解: 我们可以把Promise当做一台自动贩卖机:我们投币(发起请求)后,贩卖机进入"pending"状态,最终要么出货(fulfilled),要么退币(rejected)。一旦出货或退币完成,状态就固定了,不会突然又变回处理中。

Promise的三态模型:状态流转

状态定义与转换关系

Promise规范明确定义了三种状态:pending(待定)fulfilled(兑现)rejected(拒绝),它们构成了一个完整的有限状态机。我们先通过一段简单的代码示例,来观察Promise的状态流程:

javascript 复制代码
// 状态流转示意图
// 创建时 → pending
// pending → fulfilled (通过resolve)
// pending → rejected (通过reject)

// 创建Promise时的初始状态
const promise = new Promise((resolve, reject) => {
    console.log('Promise创建,状态: pending');
    
    // 1秒后状态转换
    setTimeout(() => {
        const success = Math.random() > 0.5;
        
        if (success) {
            resolve('操作成功');  // pending → fulfilled
            console.log('状态已变为: fulfilled');
        } else {
            reject(new Error('操作失败'));  // pending → rejected
            console.log('状态已变为: rejected');
        }
        
        // 注意:以下代码不会生效!
        if (!success) {
            resolve('重新尝试成功');  // 无效!状态已锁定
        }
    }, 1000);
});

状态详解

Pending(待定状态)

Pending 为Promise的初始状态,也可以称为过渡状态,它可能被转为fulfilled或rejected,通常在创建Promise时默认即为pending状态,类似于订单已下单,但商家尚未发货。我们可以看下面一个例子:

javascript 复制代码
const myPromise = new Promise(() => { })
console.log(myPromise)

上述代码中,我们创建了一个空函数对象,其输出结果为:Promise { <pending> }。这是Promise对象的最初始状态,在pending状态下,Promise可以被转换成fulfilledrejected

注:Promise并不是一开始就必须处于pending状态,通过Promise.resolve()等静态方法,可以直接实例化一个resolved状态的Promise对象。
Promise相关实例化方法和静态方法在后面的文章中会详细讲解。

Fulfilled(兑现状态)

resolve(value) 被调用时,Promise对象的状态会被转换成fulfilled,同时会携带一个不可变的结果值,就好比订单已发货并签收。我们可以看下面一个例子:

javascript 复制代码
const myPromise = new Promise((resolve, reject) => resolve('Hello, World!'))
console.log(myPromise)

上述代码中,我们在Promise中产生了一个resolve('Hello, World!')的回调函数,其输出结果是:Promise {<fulfilled>: 'Hello, World!'}

注:在不同的编辑器中,解析结果可能不一样,比如node.js环境中,会直接输出Promise { 'Hello, World!'}

Rejected(拒绝状态)

reject(reason)被调用时,或同步抛出异常问题,Promise对象的状态会被转换成rejected,同时会携带一个拒绝原因(通常是Error对象),就好比订单因缺货等原因无法完成。我们可以看下面一个例子:

javascript 复制代码
const myPromise = new Promise((resolve, reject) => reject(new Error('直接拒绝')))
console.log(myPromise)

上述代码中,我们在Promise中产生了一个reject(new Error('直接拒绝'))的回调函数,其输出结果是:Promise {<rejected>: Error: 直接拒绝

注:在不同的编辑器中,解析结果可能不一样,比如node.js环境中,会直接输出Promise {<rejected>: Error: 直接拒绝} 外,还是打印出堆栈跟踪信息。

除了上述代码示例外,下面两种情况也会自动转成rejected

javascript 复制代码
const myPromise = new Promise((resolve, reject) => {
    throw new Error('同步异常导致拒绝');  // 会被自动转换为reject
})
console.log(myPromise)
javascript 复制代码
const myPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
        try {
            JSON.parse('无效的JSON')
        } catch (err) {
            reject(err)  // 捕获异常并明确拒绝
        }
    }, 0)
})
console.log(myPromise)

不可变原则

状态不可逆性

这是Promise最重要的特性之一:状态一旦从 pending 变为 fulfilledrejected ,就永远不会再次改变。我们可以看下面一个例子:

javascript 复制代码
// 状态不可逆的证明
const myPromise = new Promise((resolve, reject) => {
    // 第一次状态改变
    resolve('第一次成功');

    // 以下所有尝试都不会生效
    resolve('第二次成功');  // 忽略
    reject(new Error('尝试失败'));  // 忽略
    setTimeout(() => {
        resolve('延迟的成功');  // 忽略
    }, 1000);

    // 甚至抛出异常也不会影响
    throw new Error('异常被忽略');
});

myPromise
    .then(value => {
        console.log('只会收到:', value);  // 输出: 第一次成功
        return value;
    })
    .catch(err => {
        console.log('永远不会执行这里');  // 因为状态已经是fulfilled
    });

上述代码的执行结果只会是:只会收到: 第一次成功

值不可变性

当Promise被决议后,其状态就会被确定,同时它所携带的值就固定不变了,无法从外部修改。我们来看下面一个例子:

javascript 复制代码
const myPromise = new Promise((resolve, reject) => {
    resolve('第一次设置的值') // 有效
    resolve('尝试覆盖的值')   // 被忽略
    reject(new Error('尝试拒绝')) // 被忽略
})

myPromise.then(
    value => console.log('成功:', value), // 输出: 成功: 第一次设置的值
    error => console.log('失败:', error.message) // 不会执行
)

上述代码的执行结果只会是:成功: 第一次设置的值

结语

本文主要介绍了Promise状态机与状态流转的相关概念,如果你在理解状态机模型或不可变原则时有任何疑问,或对于文章中错误的地方或者有任何问题,欢迎在评论区留言讨论!

相关推荐
baozj7 小时前
给 Ant Design Vue 装上"涡轮增压":虚拟列表封装实践
前端·javascript·vue.js
ohyeah7 小时前
构建现代 React 登录表单:从 ESM 懒加载到 TailwindCSS 响应式设计
前端·react.js
holidaypenguin7 小时前
常用MCP记录
前端·mcp
周星星日记7 小时前
一个例子搞懂vite快再哪里
前端·面试
一只爱吃糖的小羊7 小时前
AbortController 深度解析:Web 开发中的“紧急停止开关”
前端
码界奇点7 小时前
前端基础知识构建现代Web应用的基石
前端·青少年编程·web
C_心欲无痕7 小时前
网络相关 - Fetch 与 Ajax 请求讲解
前端·网络协议·ajax·fetch
得物技术7 小时前
前端平台大仓应用稳定性治理之路|得物技术
前端
Eaxker8 小时前
前端工程化
前端