本专栏聚焦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可以被转换成fulfilled或rejected。
注: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 变为 fulfilled 或 rejected ,就永远不会再次改变。我们可以看下面一个例子:
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状态机与状态流转的相关概念,如果你在理解状态机模型或不可变原则时有任何疑问,或对于文章中错误的地方或者有任何问题,欢迎在评论区留言讨论!