什么是Promise
Promise 是 JavaScript 中用于处理异步操作的对象,它代表一个尚未完成但预计将来会完成的操作。
Promise的运行过程
new MyPromise((resolve,reject) => {
setTimeout(resolve("test"),1000)
}).then(res){
console.log("then回调: "+res)
}
首先需要知道JS的事件机制:
宏任务:script,setTimeout,setInterval
微任务:Promise,then
优先级:主线程>微任务>宏任务
也就是说在当前代码中,时延函数中代码最晚运行,此时先执行then,将then中的函数存入一个微任务队列中,等实验代码中的resolve执行完毕后,才运行then中的函数
-
同步代码执行阶段
- 创建 Promise 实例,立即执行 executor 函数
- 遇到
setTimeout,将其回调函数放入宏任务队列 - 执行
.then()方法,将成功回调函数存入微任务队列
-
事件循环处理阶段
- 主线程执行完毕
- 检查微任务队列,发现 Promise 的 then 回调在等待
- 但此时 Promise 仍处于 pending 状态,回调暂不执行
-
异步操作完成阶段
- 1秒后,setTimeout 回调进入宏任务队列
- 执行
resolve("操作成功"),Promise 状态变为 fulfilled - 触发 then 回调执行,输出:"then回调: 操作成功"
简易Promise实现
首先创建一个Promise类的大体架构,在上文大概已经知道,Promise中存在构造函数,resolve,reject,then这几个内部函数,以及状态值,value值和任务队列
class MyPromise {
#state = 'pending'
#value = null
//任务队列
#onFulfilledCallBacks = [];
#onRejectedCallBacks = [];
constructor(excutor) {
try {
excutor()
} catch(err) {
console.log(err)
}
}
#resolve(res) {
}
#reject(err) {
}
#then(onSuccess,onFail) {
}
}
实现构造函数
constructor(excutor) {
try {
excutor(this.#resolve.bind(this),this.#reject.bind(this))
}catch(err) {
this.#reject(err)
}
}
excutor(this.#resolve.bind(this),this.#reject.bind(this)),将resolve和reject正确绑定到执行上下文中。如果不绑定,调用resolve时,resolve中的this会指向undefined
前后两个this的问题
两个this都指向new MyPromise这一实例对象,由于this.#resolve是引用了这个函数,并不是调用,并不能将resolve正确绑定到实例对象上,所以需要bind绑定this
实现resolve
将 Promise 对象的状态从 PENDING 变为 FULFILLED,并执行成功后的注册任务
#resolve(res) {
if(this.#state === 'pending') {
this.#state = 'fufilled'
this.#value = res
this.#onFulfilledCallBacks.forEach((callback) => callback(res))
}
}
这里的callback是then中的回调函数
实现reject
将 Promise 对象的状态从 PENDING 变为 REJECTED,并执行失败后的注册任务
#reject(err) {
if(this.#state === 'pending') {
this.#state = 'rejected'
this.#value = err
this.#onRejectedCallBacks.forEach((callback) => callback(err))
}
}
实现then
因为then可能会需要链式调用,所以它需要返回一个Promise的对象,并且then中的回调也可能是一个Promise的对象,这时就需要考虑通过递归处理内部的Promise了
then(onSuccess, onFail) {
return new MyPromise((resolve, reject) => {
if (this.status === "pending") {
if (typeof onSuccess === "function") {
this.#onFulfilledCallBacks.push(() => {
const fulfilledRet = onSuccess(this.value);
// 判断then中的回调函数是不是一个Promise对象,如果是,那么需要递归再进行处理
if (fulfilledRet instanceof MyPromise) {
//递归入口
fulfilledRet.then(resolve, reject);
} else {
resolve(fulfilledRet);
}
});
}
if (typeof onFail === "function") {
this.#onRejectedCallBacks.push(() => {
const rejectReason = onFail(this.value);
if (rejectReason instanceof MyPromise) {
rejectReason.then(resolve, reject);
} else {
reject(rejectReason);
}
});
}
}
if (this.status === "fulfilled") {
const fulfilledRet = onSuccess(this.value);
if (fulfilledRet instanceof MyPromise) {
fulfilledRet.then(resolve, reject);
} else {
resolve(fulfilledRet);
}
}
if (this.status === "rejected") {
const rejectReason = onFail(this.value);
if (rejectReason instanceof MyPromise) {
rejectReason.then(resolve, reject);
} else {
reject(rejectReason);
}
}
});
}
这一段代码非常巧妙非常重要,深入理解
this.#onFulfilledCallBacks.push(() => {
const fulfilledRet = onSuccess(this.value);
// 判断then中的回调函数是不是一个Promise对象,如果是,那么需要递归再进行处理
if (fulfilledRet instanceof MyPromise) {
//递归入口
fulfilledRet.then(resolve, reject);
} else {
resolve(fulfilledRet);
}
});
向队列中压入了一个箭头函数,作用是:
1. 延迟执行时机
// ❌ 错误方式:直接存储函数引用
this.#onFulfilledCallBacks.push(onSuccess);
// ✅ 正确方式:包装成回调函数
this.#onFulfilledCallBacks.push(() => {
const fulfilledRet = onSuccess(this.value);
// 处理逻辑...
});
区别:
- 直接存储
onSuccess:执行时无法访问最新的this.value - 包装回调:执行时能访问到 Promise 完成后的最新值
2. 确保正确的上下文访问
const promise = new MyPromise(resolve => {
setTimeout(() => resolve("最终值"), 1000);
});
promise.then(value => {
console.log(value); // 需要确保这里拿到的是 "最终值",而不是 undefined
});
执行时机对比:
// 如果直接 push(onSuccess)
// 当 resolve("最终值") 被调用时:
this.value = "最终值";
this.#onFulfilledCallBacks.forEach(callback => callback());
// 此时 callback 就是 onSuccess,但 this.value 已经正确设置
// 实际上直接 push(onSuccess) 也能工作,但...
3. 真正的关键:统一返回值处理逻辑
这才是包装回调的核心价值:
this.#onFulfilledCallBacks.push(() => {
const fulfilledRet = onSuccess(this.value); // 1. 执行用户回调
// 2. 统一处理返回值(这才是关键!)
if (fulfilledRet instanceof MyPromise) {
fulfilledRet.then(resolve, reject); // Promise 值:状态传递
} else {
resolve(fulfilledRet); // 普通值:直接 resolve
}
});
4. 为什么不能直接存储 onSuccess
如果直接存储 onSuccess,就需要在别处处理返回值:
// ❌ 混乱的实现
this.#onFulfilledCallBacks.push(onSuccess);
// 然后在 resolve 方法中:
resolve(value) {
this.value = value;
this.#onFulfilledCallBacks.forEach(callback => {
const result = callback(this.value); // 执行回调
// 但这里无法访问新Promise的resolve/reject!
// 因为新Promise是在then方法中创建的
});
}
5. 闭包捕获 resolve/reject
包装回调通过闭包捕获了新 Promise 的 resolve/reject:
then(onSuccess, onFail) {
return new MyPromise((resolve, reject) => { // 新Promise的resolve/reject
if (this.status === "pending") {
this.#onFulfilledCallBacks.push(() => {
// 这个箭头函数捕获了外部的resolve和reject!
const fulfilledRet = onSuccess(this.value);
if (fulfilledRet instanceof MyPromise) {
fulfilledRet.then(resolve, reject); // 使用捕获的resolve/reject
} else {
resolve(fulfilledRet); // 使用捕获的resolve
}
});
}
});
}
6. 实际执行流程演示
const promise = new MyPromise(resolve => {
setTimeout(() => resolve(10), 1000);
});
const promise2 = promise.then(value => {
console.log("拿到值:", value); // 10
return value * 2;
});
// 执行过程:
// 1. promise.then 被调用,创建新Promise,包装回调被push到队列
// 2. 1秒后 promise resolve(10)
// 3. 执行包装的回调:onSuccess(10) → 返回20
// 4. 执行 resolve(20) → promise2 变为 fulfilled,值为20
总结
使用回调函数包装的核心意义:
- 统一处理:将用户回调执行和返回值处理封装在一起
- 闭包捕获:通过闭包捕获新 Promise 的 resolve/reject,用于处理返回值
- 时机控制:确保在 Promise 状态确定后才执行用户回调
- 值传递:正确处理普通值和 Promise 值的不同传递方式
实现catch
catch底层与then一样,直接使用then即可
catch(onFail) {
return this.then(null, onFail);
}