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状态机与状态流转的相关概念,如果你在理解状态机模型或不可变原则时有任何疑问,或对于文章中错误的地方或者有任何问题,欢迎在评论区留言讨论!

相关推荐
快乐小土豆~~2 分钟前
echarts柱状图的X轴label过长被重叠覆盖
前端·javascript·vue.js·echarts
hhcccchh11 分钟前
1.1 HTML 语义化标签(header、nav、main、section、footer 等)
java·前端·html
小李子呢021135 分钟前
前端八股2---Proxy 代理
前端·javascript·vue.js
bjzhang751 小时前
使用 HTML + JavaScript 实现组织架构图
前端·javascript·html·组织架构图
军军君011 小时前
Three.js基础功能学习十六:智能黑板实现实例三
前端·javascript·css·vue.js·3d·前端框架·threejs
海上彼尚1 小时前
SVG矢量图形快速入门
前端·html5
嗷o嗷o1 小时前
Android App Functions 深入理解
前端
UXbot1 小时前
AI原型设计工具评测:从创意到交互式Demo,5款产品全面解析
前端·ui·设计模式·ai·ai编程·原型模式
落魄江湖行2 小时前
硅基同事埋的坑,我用2小时才填平:Nuxt 4 路由踩坑:可选参数 [[id]] 与 [id] 的区别
前端
一勺菠萝丶2 小时前
管理后台使用手册在线预览与首次登录引导弹窗实现
java·前端·数据库