一文带你剖析 Promise 实现原理,状态机、发布订阅模式完美实现异步编程
注:本文代码为仿promise原理简写的代码,非源码。
1. 概述
本文档详细解析了基于 ES6 Class 实现的 Promise,包括其核心原理、状态管理机制、异步处理流程以及各种方法的实现细节。通过深入理解 Promise 的内部实现,帮助你更好地掌握异步编程范式。
2. Promise 核心原理
2.1 状态机设计
Promise 采用状态机模式,具有三种互斥状态:
- pending: 初始状态,既未完成也未拒绝
- fulfilled/resolved: 操作成功完成
- rejected: 操作失败
核心特性:状态一旦改变,就不会再变,这是 Promise 可靠性的基础。
javascript
// 状态定义与初始化
constructor(executeFn) {
this.status = 'pending' // 初始状态为 pending
this.value = undefined // 存储成功结果
this.reason = undefined // 存储失败原因
this.onResolvedCallbacks = [] // 成功回调队列
this.onRejectedCallbacks = [] // 失败回调队列
// ...
}
2.2 发布-订阅模式
Promise 内部采用发布-订阅模式处理异步回调:
- 在 pending 状态时,通过 then/catch 方法注册的回调会被存储在回调队列中
- 当 Promise 状态改变时,会遍历执行对应队列中的所有回调(constructor中通过resolve或reject函数触发回调)
这解决了异步操作和回调注册的时序问题,确保即使回调在状态变更前注册,也能在状态变更后得到执行。
3. 核心实现详解
3.1 构造函数实现 💖
构造函数接收一个 executeFn 函数,立即执行该函数并传入 resolve 和 reject 两个函数作为参数:
javascript
constructor(executeFn) {
// 初始化状态和数据
this.status = 'pending'
this.value = undefined
this.reason = undefined
this.onResolvedCallbacks = []
this.onRejectedCallbacks = []
// 使用箭头函数定义 resolve/reject,确保 this 指向 Promise 实例
const resolve = (value) => {
if (this.status === 'pending') {
this.status = 'fulfilled'
this.value = value
// 发布成功事件,执行所有成功回调
this.onResolvedCallbacks.forEach(fn => fn(this.value))
}
}
const reject = (reason) => {
if (this.status === 'pending') {
this.status = 'rejected'
this.reason = reason
// 发布失败事件,执行所有失败回调
this.onRejectedCallbacks.forEach(fn => fn(this.reason))
}
}
// 捕获 executeFn 执行过程中的异常
try {
executeFn(resolve, reject)
} catch (error) {
reject(error)
}
}
3.2 then 方法实现💖
then 方法是 Promise 规范(Promise/A+)的核心,它是 Promise 能够实现异步操作链式调用的关键机制,被誉为 JavaScript 异步编程的革命性突破。其重要性体现在:
- 彻底解决回调地狱问题:通过链式调用替代嵌套回调,使异步代码可读性大幅提升
- 统一异步操作接口:为所有异步操作提供了标准化的处理方式
- 错误传播机制:实现了错误在链式调用中的自动传递,无需每层都添加错误处理
- 状态隔离与数据传递:保证了异步操作结果的正确传递和状态的严格隔离
- 异步操作编排:支持复杂异步操作流程的构建和组合
核心要点:
-
值穿透机制(参数校验与默认处理)
- 当
onFulfilled或onRejected不是函数时(初始化为函数),Promise 规范要求实现「值穿透」 - 成功状态的值穿透:
value => value- 确保成功的值能够继续在链中传递 - 失败状态的值穿透:
reason => { throw reason }- 确保错误能够继续在链中传播
- 当
-
这一机制使得可以在链式调用中间跳过某些处理步骤而不中断链式调用
当 then 方法不传递回调函数时,会使用默认函数将值传递给下一个 then 方法,实现值的穿透。
-
链式调用实现(Promise 链)
- 每次调用
then方法都返回一个全新的 Promise 实例(promise2) - 这是链式调用的基础,保证了每个
then操作都在独立的 Promise 上下文中执行
- 每次调用
- 新 Promise 的状态由回调函数的执行结果决定,实现了状态的隔离和转换
-
状态驱动的行为模式---状态机
- 根据当前 Promise 的状态 (pending/fulfilled/rejected)采取不同的处理策略
- 已完成状态(fulfilled/rejected):立即异步执行对应回调
- 进行中状态(pending):将回调函数存储到对应队列中等待执行
- 这体现了状态模式的设计思想,行为随状态而变化
-
异步执行保证
- 使用
setTimeout(fn, 0)模拟微任务,确保回调函数异步执行 - 这符合 Promise/A+ 规范要求,保证了回调执行的时序一致性
- 避免了同步执行可能导致的状态不一致和执行顺序问题
- 使用
-
结果处理与值传递
- 通过
resolvePromise辅助函数处理回调返回值,支持:- 返回 Promise 对象:等待该 Promise 解决并采用其结果
- 返回普通值:直接将该值作为下一个 Promise 的成功值
- 抛出异常:将异常作为下一个 Promise 的失败原因
- 这一设计实现了值在异步链中的自然传递和转换
- 通过
javascript
then(onFulfilled, onRejected) {
// 参数校验,支持值穿透.当 `onFulfilled` 或 `onRejected` 不是函数时,使用箭头函数将其转为函数。实现值穿透
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
// 返回新的 Promise 以支持链式调用
const promise2 = new MyPromise((resolve, reject) => {
// 成功状态处理
if (this.status === 'fulfilled') {
// 异步执行回调
setTimeout(() => {
try {
// 使用了值穿透,这里才能直接使用onFulfilled(this.value),否则就异常了
const x = onFulfilled(this.value)
// 处理回调返回值
this.resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
}
// 失败状态处理
if (this.status === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason)
this.resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
}
// pending 状态处理 - 存储回调
// 订阅过程(注册回调)
if (this.status === 'pending') {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value)
this.resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason)
this.resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
}
})
return promise2
}
3.3 resolvePromise 辅助函数--处理 then 方法回调函数的返回值💖
核心作用与重要性
resolvePromise 是 Promise 实现中最为复杂和核心的辅助函数,它是 Promise/A+ 规范中实现 "Promise Resolution Procedure"(Promise 解决过程)的关键。这个函数负责处理 then 方法回调函数的返回值 ,并根据返回值类型决定如何处理下一个 Promise 的状态转换,是实现链式调用和值传递的核心机制。
参数说明
javascript
resolvePromise(promise2, x, resolve, reject)
promise2:then方法返回的新 Promise 实例x:then方法回调函数的返回值(onFulfilled 或 onRejected 的返回值)resolve: promise2 的 resolve 函数,用于将 promise2 状态改为 fulfilledreject: promise2 的 reject 函数,用于将 promise2 状态改为 rejected
详细要点:
-
循环引用检测
- 当
x与promise2是同一个对象时,会导致无限循环 - 通过
if (promise2 === x)判断避免这种情况,并抛出类型错误 - 这是 Promise/A+ 规范强制要求的安全机制
- 当
-
状态凝固保障
- 使用
called标志变量确保resolve或reject只能被调用一次 - 这符合 Promise 的核心特性:状态一旦改变就不能再变
- 在所有可能调用
resolve/reject的地方都检查called标志
- 使用
-
Promise 类型处理
- 当
x是 Promise 实例时,需要等待其状态变化 - 递归调用
x.then(),将结果继续通过resolvePromise处理 - 确保 Promise 链能够正确等待异步操作完成
- 当
-
thenable 对象兼容
- "thenable" 对象是指具有
then方法的对象或函数 - 通过动态获取
x.then属性并检查其是否为函数来识别 thenable 对象 - 使用
then.call(x, ...)调用 then 方法,确保上下文正确绑定 - 这是 Promise 能够兼容其他 Promise 实现的关键
- "thenable" 对象是指具有
-
异常处理机制
- 使用 try-catch 捕获访问
x.then属性或调用 then 方法时可能发生的异常 - 任何异常都将导致 promise2 被拒绝,错误作为拒绝原因
- 异常处理也受到
called标志的保护,确保只被处理一次
- 使用 try-catch 捕获访问
-
原始值直接传递
- 当
x不是对象或函数时(原始值),直接调用resolve(x) - 这确保了普通值能够正确地传递到下一个 Promise
- 当
代码执行流程
resolvePromise 的执行流程体现了 Promise/A+ 规范的严格要求:
- 首先检查循环引用 → 2. 初始化 called 标志 → 3. 根据 x 的类型分三种情况处理:
- Promise 实例 → 等待其状态变化并递归处理
- 对象/函数 → 检查是否为 thenable 并相应处理
- 原始值 → 直接 resolve
为什么需要这个复杂的函数?
resolvePromise 函数解决了 JavaScript 异步编程中的几个关键问题:
- 统一的异步接口:无论返回什么类型的值,都能按照统一规则处理
- 跨 Promise 实现兼容:通过 thenable 机制支持不同的 Promise 实现
- 防止状态不一致:通过 called 标志确保状态只变更一次
- 避免死循环:通过循环引用检测确保程序安全
- 正确的值传递:在异步链中确保值能够正确地从一个 Promise 传递到下一个
javascript
resolvePromise(promise2, x, resolve, reject) {
// 处理循环引用 1.循环引用检测:当 `x` 与 `promise2` 是同一个对象时,会导致无限循环
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'))
}
// 防止多次调用 2.状态凝固保障:状态一旦改变就不能再变;确保 `resolve` 或 `reject` 只能被调用一次
let called = false
// 处理 x 是 Promise 的情况 3.Promise 类型处理:当 `x` 是 Promise 实例时,需要等待其状态变化。
if (x instanceof MyPromise) {
//递归调用 `x.then()`,将结果继续通过 `resolvePromise` 处理
x.then(
value => this.resolvePromise(promise2, value, resolve, reject),
reason => reject(reason)
)
}
// 4.thenable 对象兼容:"thenable" 对象是指具有 `then` 方法的对象或函数
// 处理 x 是对象或函数的情况(可能是 thenable)
else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
// 5.异常处理机制:使用 try-catch 捕获访问 `x.then` 属性或调用 then 方法时可能发生的异常
try {
// 动态获取 `x.then` 属性并检查其是否为函数来识别 thenable 对象
const then = x.then
// 判断是否为 thenable 对象
if (typeof then === 'function') {
// 使用 `then.call(x, ...)` 调用 then 方法,确保上下文正确绑定
// 这是处理 thenable 对象的核心逻辑
then.call(
x, // 确保 then 方法内部的 this 指向 x 对象
// 成功回调函数 - 当 thenable 对象被成功解析时执行
value => {
// 状态凝固检查:如果已经调用过 resolve/reject,则直接返回
if (called) return
// 标记为已调用,防止重复调用
called = true
// 递归调用 resolvePromise 处理 thenable 对象返回的 value
// 这是实现链式调用和值传递的关键步骤
// 例如:当 value 本身也是 Promise 或 thenable 时,会继续递归处理
this.resolvePromise(promise2, value, resolve, reject)
},
// 失败回调函数 - 当 thenable 对象被拒绝时执行
reason => {
// 状态凝固检查
if (called) return
// 标记为已调用
called = true
// 直接拒绝 promise2,并将错误原因传递下去
reject(reason)
}
)
} else {
// 普通对象,直接 resolve
resolve(x)
}
} catch (error) {
if (called) return
called = true
reject(error) // 错误作为拒绝原因
}
} else {
// 原始值,直接 resolve 6.原始值直接传递
resolve(x)
}
}
JavaScript中的 call 方法详解
call方法是JavaScript中函数对象的一个内置方法,它允许你调用一个函数并明确指定函数内部的this值。这在Promise实现中非常重要,特别是处理thenable对象时。基本语法
javascriptfunction.call(thisArg, arg1, arg2, ...)主要作用
- 改变函数内部的
this指向
- 第一个参数
thisArg就是函数执行时this的指向- 后续参数作为函数的参数传入
为什么需要 call 方法?
在JavaScript中,函数内部的
this指向通常由调用方式决定,而不是定义方式 。call方法提供了一种显式控制this指向的机制,这在很多场景下非常有用,尤其是在处理对象方法借用、回调函数、类继承等情况时。Promise实现中的应用
在Promise实现中,
call方法主要用于处理thenable对象:
javascriptthen.call( x, // 确保 then 方法内部的 this 指向 x 对象 value => { /* 成功回调 */ }, reason => { /* 失败回调 */ } )这里为什么要用call?
- thenable对象是指具有
then方法的对象- 我们需要调用这个对象的
then方法,但同时要确保这个方法内部的this正确指向该对象本身- 如果直接写
x.then(...),在正常情况下this也会指向x,但使用call方法是更明确、更安全的做法,特别是在某些特殊情况下(如函数被赋值给变量后调用)简单例子说明
javascript// 定义一个对象 const person = { name: 'John', greet: function(greeting) { console.log(`${greeting}, ${this.name}!`); } }; // 正常调用 - this指向person person.greet('Hello'); // 输出: "Hello, John!" // 使用call改变this指向 const anotherPerson = { name: 'Jane' }; person.greet.call(anotherPerson, 'Hi'); // 输出: "Hi, Jane!"与Promise thenable处理的关系
在Promise的
resolvePromise函数中:
- 我们检测到一个对象有
then方法- 我们需要调用这个
then方法,但必须确保它在正确的this上下文中执行- 使用
then.call(x, onFulfilled, onRejected)确保了:
then方法被调用then方法内部的this指向x对象- 我们传入了成功和失败的回调函数
这种方式是Promise/A+规范要求的标准做法,确保了不同Promise实现之间的兼容性和正确的上下文绑定。
4. Promise 扩展方法实现
4.1 错误处理方法
catch 方法
专门用于捕获 Promise 链中的错误,本质是 then 方法的语法糖:
javascript
catch(onRejected) {
return this.then(null, onRejected)
}
finally 方法
无论 Promise 成功或失败都会执行的回调,不接收参数也不影响原 Promise 的结果:
javascript
finally(callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value),
reason => MyPromise.resolve(callback()).then(() => { throw reason })
)
}
4.2 静态方法
resolve 方法
返回一个已成功的 Promise,如果参数本身是 Promise 则直接返回:
javascript
static resolve(value) {
if (value instanceof MyPromise) {
return value
}
return new MyPromise(resolve => resolve(value))
}
reject 方法
返回一个已失败的 Promise:
javascript
static reject(reason) {
return new MyPromise((resolve, reject) => reject(reason))
}
all 方法💖
等待所有 Promise 都成功,返回包含所有结果的数组;任一 Promise 失败则整体失败:
javascript
static all(promises) {
return new MyPromise((resolve, reject) => {
const result = []
let count = 0
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument must be an array'))
}
if (promises.length === 0) {
return resolve([])
}
promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(
value => {
result[index] = value
count++
if (count === promises.length) {
resolve(result)
}
},
reason => reject(reason)
)
})
})
}
race 方法
返回第一个完成的 Promise 的结果(无论成功或失败):
javascript
static race(promises) {
return new MyPromise((resolve, reject) => {
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument must be an array'))
}
if (promises.length === 0) {
return
}
promises.forEach(promise => {
MyPromise.resolve(promise).then(
value => resolve(value),
reason => reject(reason)
)
})
})
}
5.constructor中的resolve函数和外部的static resolve方法有何区别?
在Promise实现中,constructor中的resolve函数和外部的static resolve方法有以下几个关键区别:
5.1. 定义位置与类型
- constructor中的resolve:是在Promise构造函数内部定义的局部函数,作为参数传递给executor函数,使用箭头函数定义以确保this指向Promise实例。
- static resolve方法 :是Promise类的静态方法,通过
MyPromise.resolve()直接调用,不属于任何实例。
5.2. 主要作用
-
constructor中的resolve:
- 负责将Promise实例的状态从pending转变为fulfilled
- 设置成功值(value)并触发所有注册的成功回调函数
- 是Promise内部状态转换的核心机制
-
static resolve方法:
- 作为Promise的工具方法,用于快速创建一个已成功状态的Promise实例
- 提供Promise包装功能,将任意值转换为Promise
5.3. 调用方式
-
constructor中的resolve:
- 由executor函数内部调用,通常是异步操作完成后
- 例如:
new MyPromise((resolve, reject) => { setTimeout(() => resolve(1), 1000); })
-
static resolve方法:
- 直接通过类名调用,作为工厂方法使用
- 例如:
MyPromise.resolve(1)或MyPromise.resolve(promiseInstance)
5.4. 处理逻辑
-
constructor中的resolve:
- 检查当前状态是否为pending,确保状态只能变更一次
- 更新状态为fulfilled并存储成功值
- 异步执行所有已注册的成功回调
-
static resolve方法:
- 检查传入值是否已经是Promise实例,如果是则直接返回
- 否则创建并返回一个新的已成功状态的Promise实例
5.5. 代码实现对比
javascript
// constructor中的resolve(局部函数)
const resolve = (value) => {
if (this.status === 'pending') {
this.status = 'fulfilled'
this.value = value
this.onResolvedCallbacks.forEach(fn => fn(this.value))
}
}
// static resolve方法(类静态方法)
static resolve(value) {
if (value instanceof MyPromise) {
return value
}
return new MyPromise(resolve => resolve(value))
}
总结来说,constructor中的resolve是Promise内部状态管理的核心机制,负责状态转换和回调触发;而static resolve则是一个便捷的工具方法,用于创建已解决状态的Promise实例,实现值的Promise化包装。
6. 上述代码优化建议
6.1 微任务模拟改进then 方法中 setTimeout
当前实现使用 setTimeout 模拟异步执行,但实际的 Promise 使用微任务队列。在浏览器环境中,可以使用 MutationObserver 或 MessageChannel 更好地模拟微任务:
javascript
// 微任务队列实现
const microTask = (fn) => {
if (typeof queueMicrotask === 'function') {
queueMicrotask(fn);
} else if (typeof MutationObserver === 'function') {
const observer = new MutationObserver(fn);
const textNode = document.createTextNode('');
observer.observe(textNode, { characterData: true });
textNode.data = '1';
} else {
setTimeout(fn, 0);
}
};
// 然后在 then 方法中使用 microTask 替代 setTimeout
6.2 增强静态方法
可以扩展更多实用的静态方法,如:
javascript
// allSettled 方法 - 等待所有 Promise 完成,不关心成功或失败
static allSettled(promises) {
return new MyPromise(resolve => {
const results = [];
let count = 0;
if (!Array.isArray(promises)) {
return resolve([]);
}
if (promises.length === 0) {
return resolve([]);
}
promises.forEach((promise, index) => {
MyPromise.resolve(promise)
.then(
value => {
results[index] = { status: 'fulfilled', value };
count++;
if (count === promises.length) {
resolve(results);
}
},
reason => {
results[index] = { status: 'rejected', reason };
count++;
if (count === promises.length) {
resolve(results);
}
}
);
});
});
}
// any 方法 - 返回第一个成功的 Promise
static any(promises) {
return new MyPromise((resolve, reject) => {
const errors = [];
let count = 0;
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument must be an array'));
}
if (promises.length === 0) {
return reject(new AggregateError([], 'All promises were rejected'));
}
promises.forEach((promise, index) => {
MyPromise.resolve(promise)
.then(resolve)
.catch(error => {
errors[index] = error;
count++;
if (count === promises.length) {
reject(new AggregateError(errors, 'All promises were rejected'));
}
});
});
});
}
7. 输入输出示例
基本用法示例
javascript
// 创建并使用 Promise
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('成功了!');
// reject('失败了!');
}, 1000);
});
promise.then(
value => {
console.log('成功:', value); // 输出: 成功: 成功了!
return '链式调用';
}
).then(
value => {
console.log('链式调用结果:', value); // 输出: 链式调用结果: 链式调用
}
).catch(
reason => {
console.log('失败:', reason);
}
);
静态方法使用示例
javascript
// Promise.all 示例
MyPromise.all([
MyPromise.resolve(1),
new MyPromise(resolve => setTimeout(() => resolve(2), 100)),
3
]).then(values => {
console.log('all结果:', values); // 输出: all结果: [1, 2, 3]
});
// Promise.race 示例
MyPromise.race([
new MyPromise(resolve => setTimeout(() => resolve(1), 100)),
new MyPromise((resolve, reject) => setTimeout(() => reject(2), 50))
]).then(
value => console.log('race成功:', value),
reason => console.log('race失败:', reason) // 输出: race失败: 2
);
8. 总结
本文详细解析了 Promise 的实现原理,包括状态机设计、发布-订阅模式、异步执行机制以及各种方法的实现细节。通过深入理解这些核心概念,开发者可以更好地掌握异步编程范式,编写更加健壮和高效的异步代码。
Promise 的实现体现了优秀的设计模式应用,特别是状态机和发布-订阅模式的结合,解决了传统回调地狱问题,提供了更加优雅和可维护的异步编程解决方案。
思考1:状态机模式是什么?怎么在Promise中实现
状态机模式的定义
状态机模式是一种行为设计模式 ,它允许对象在内部状态改变时改变其行为 。对象看起来好像修改了它的类,因为它的行为随着状态的变化而变化 。在软件设计中,状态机模式特别适用于描述对象在其生命周期内可能经历的状态转换,以及这些状态转换所触发的行为。
在经典的设计模式分类 中,我们通常不会直接使用"状态机模式"这个术语,而是使用 状态模式(State Pattern) 。状态机是一个更广泛的概念,而状态模式是实现状态机的一种面向对象设计模式。
总结
- 状态模式(State Pattern) :GoF正式定义的设计模式,属于23种经典设计模式之一。
- 状态机模式 :更侧重于实现有限状态机概念的模式 ,在工程实践中的常用称呼。
- Promise的实现同时符合状态模式的设计原则和状态机的核心概念,所以可以说它使用了状态模式(在设计模式术语中)或状态机模式(在工程实践术语中)。
这就是为什么你可能在不同的资料中看到不同的称呼,但它们描述的是同一个核心设计思想。
Promise中的状态机实现
在promise-implementation-docs.md文档中,Promise通过以下方式实现了状态机模式:
1. 状态定义
Promise具有三种互斥的状态:
- pending: 初始状态,既未完成也未拒绝
- fulfilled/resolved: 操作成功完成
- rejected: 操作失败
2. 状态的不可变性
核心特性是:状态一旦改变,就不会再变。这是Promise可靠性的基础,确保了异步操作的结果一旦确定就不会被覆盖。
3. 状态实现代码
在构造函数中初始化状态和相关数据:
javascript
constructor(executeFn) {
// 初始化状态和数据
this.status = 'pending' // 初始状态为 pending
this.value = undefined // 存储成功结果
this.reason = undefined // 存储失败原因
this.onResolvedCallbacks = [] // 成功回调队列
this.onRejectedCallbacks = [] // 失败回调队列
// ...
}
4. 状态转换机制
通过resolve和reject函数实现状态转换:
javascript
const resolve = (value) => {
if (this.status === 'pending') {
this.status = 'fulfilled'
this.value = value
// 发布成功事件,执行所有成功回调
this.onResolvedCallbacks.forEach(fn => fn(this.value))
}
}
const reject = (reason) => {
if (this.status === 'pending') {
this.status = 'rejected'
this.reason = reason
// 发布失败事件,执行所有失败回调
this.onRejectedCallbacks.forEach(fn => fn(this.reason))
}
}
状态机模式在Promise中的核心机制
-
状态检查 : 在转换状态前,会检查当前状态是否为
pending,只有处于pending状态的Promise才能转换为fulfilled或rejected状态。 -
状态与数据关联 : 状态转换时,会同时存储相关的数据(成功值
value或失败原因reason)。 -
状态驱动行为: 状态的变化会触发相应的行为,例如执行存储在回调队列中的函数。
-
互斥性: 三种状态是互斥的,任何时刻Promise只能处于其中一种状态。
-
单向转换 : 状态转换是单向的,只能从
pending转换到fulfilled或rejected,一旦完成转换就不能再改变。
这种状态机设计使Promise能够可靠地表示异步操作的最终结果,无论是成功还是失败,并确保异步操作的结果一旦确定就不会被覆盖,为JavaScript异步编程提供了可靠的基础。
思考2:promise中如何实现发布-订阅模式的
在MyPromise实现中,发布-订阅模式主要体现在以下几个关键部分:
1. 事件中心(constructor中定义订阅列表)
在Promise构造函数中定义了两个数组作为订阅者列表:
javascript
// 存储Promise成功状态下需要执行的回调函数数组
this.onResolvedCallbacks = []
// 存储Promise失败状态下需要执行的回调函数数组
this.onRejectedCallbacks = []
这两个数组就是发布-订阅模式中的事件中心,用于存储所有注册的回调函数。
2. 订阅过程(then方法中注册回调)
当Promise处于pending状态时,通过then方法注册的回调会被添加到对应的数组中:
javascript
// pending状态处理 - 存储回调
if (this.status === 'pending') {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value)
this.resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason)
this.resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
}
这部分代码实现了订阅功能,将回调函数添加到相应的事件队列中。
3. 发布过程(constructor中通过resolve或reject函数触发回调)
当Promise状态发生变更时(通过resolve或reject函数),会遍历对应的回调数组并执行所有订阅的回调:
javascript
const resolve = (value) => {
if (this.status === 'pending') {
this.status = 'fulfilled'
this.value = value
// 发布成功事件,执行所有注册的成功回调
this.onResolvedCallbacks.forEach(fn => fn(this.value))
}
}
const reject = (reason) => {
if (this.status === 'pending') {
this.status = 'rejected'
this.reason = reason
// 发布失败事件,执行所有注册的失败回调
this.onRejectedCallbacks.forEach(fn => fn(this.reason))
}
}
发布-订阅模式的核心体现
- 解耦性:Promise的状态变更(发布者)和回调执行(订阅者)完全解耦
- 时序无关性:无论回调是在Promise状态变更前还是变更后注册,都能正确执行
- 多订阅支持:允许多个回调订阅同一个Promise的状态变更
- 异步协调:有效解决了异步操作和回调执行的时序问题
通过这种发布-订阅模式,Promise巧妙地解决了JavaScript异步编程中的回调地狱问题,使代码更加清晰和可维护。
小朋友,你是否有很多❓
解耦性是什么意思❓怎么做到的时序无关性❓怎么支持的多订阅❓异步协调是then方法中的setTimeout,怎么解决的时序问题❓
发布-订阅模式四个核心特性的具体体现:
1. 解耦性
解耦性指的是Promise的状态status 变更机制(发布者)和回调函数(onResolvedCallbacks、onRejectedCallbacks)执行(订阅者)完全分离,彼此之间不直接依赖。
具体体现:
- 在
constructor中定义了状态管理和回调数组,这是发布者的核心部分 resolve和reject函数负责状态变更和发布事件(执行回调)then方法负责注册回调函数(pendding)到订阅列表中- 发布者(状态变更逻辑)不需要知道具体有哪些订阅者(回调函数)
- 订阅者(回调函数)也不需要知道状态何时会变更
2. 时序无关性
时序无关性确保无论回调是在Promise状态变更前还是变更后注册,都能正确执行。
具体实现:
javascript
// 在then方法中
if (this.status === 'pending') {
// 状态未变更时:存储回调到队列
this.onResolvedCallbacks.push(() => { /* 回调逻辑 */ })
} else if (this.status === 'fulfilled') {
// 状态已变更时:直接执行回调
setTimeout(() => { /* 回调逻辑 */ }, 0)
}
- 当Promise已经是fulfilled或rejected状态时,调用then方法会立即异步执行对应回调
- 当Promise还是pending状态时,回调会被存储在数组中,等待状态变更后执行
- 这样无论回调注册和状态变更的顺序如何,都能保证回调正确执行
3. 多订阅支持
多订阅 支持允许多个回调函数 订阅同一个Promise的状态变更。
具体实现:
javascript
// 在constructor中使用数组存储回调
this.onResolvedCallbacks = []
this.onRejectedCallbacks = []
// resolve函数中执行所有注册的回调
this.onResolvedCallbacks.forEach(fn => fn(this.value))
- 使用数组存储回调函数,而不是单一函数
- 每个Promise实例可以多次调用then方法 ,每次调用都会将回调添加到数组中
- 状态变更时,所有注册的回调会按添加顺序依次执行
- 这使得多个独立的操作可以基于同一个Promise的结果进行联动
4. 异步协调
异步协调通过setTimeout确保回调异步执行,解决了异步操作和回调执行的时序问题。
具体实现:
javascript
// 在then方法的三种状态处理中都使用了setTimeout
setTimeout(() => {
try {
const x = onFulfilled(this.value)
this.resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
- 使用setTimeout模拟微任务,确保回调在当前执行栈清空后执行
- 无论Promise状态是同步还是异步变更,回调总是异步执行
- 解决了回调地狱问题,提供了清晰的异步操作流程控制
- 确保即使在同步操作完成后,回调也会按预期顺序异步执行