最近在补基础,发现Promise里面有挺多东西需要理解的,函数绕来绕去的
先来一个都可看懂的代码框架,剩余的慢慢补充
js
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class MyPromise {
constructor(executor) {
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
const resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
this.onFulfilledCallbacks.forEach((fn) => fn());
}
};
const reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach((fn) => fn());
}
};
executor(resolve, reject);
}
}
首先补充then方法,由于需要链式调用,所以返回的同样是Promise对象
js
then(onFulfilled, onRejected) {
const promise2 = new MyPromise((resolve, reject) => {
const handleCallback = (callback, value, resolve, reject) => {
queueMicrotask(() => {
try {
const x = callback(value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
};
if (this.status === FULFILLED) {
handleCallback(onFulfilled, this.value, resolve, reject);
} else if (this.status === REJECTED) {
handleCallback(onRejected, this.reason, resolve, reject);
} else if (this.status === PENDING) {
this.onFulfilledCallbacks.push(() =>
handleCallback(onFulfilled, this.value, resolve, reject),
);
this.onRejectedCallbacks.push(() =>
handleCallback(onRejected, this.reason, resolve, reject),
);
}
});
return promise2;
}
handleCallback是一个工具函数,用于将用于传进来的函数进行包装,这里在then中的回调加了queueMicrotask包装了下,使它变成一个异步的任务,为什么呢 考虑两种情况
情况1: new MyPromise中立刻resolve,也就是同步的情况
js
const myPromise = new MyPromise((resolve, reject) => {
console.log("状态pending")
resolve('成功调用resolve')
})
myPromise.then(res => {
console.log(res);
}, err => {
console.log(err);
})
对于以上例子,resolve中会立刻执行回调队列中的函数,但是实例对象的then方法这个时候还没调用呢,里面是空的。
然后执行then,这个时候状态已经确定,立即执行handleCallback。
情况2:
js
const myPromise = new MyPromise((resolve, reject) => {
console.log("状态pending")
setTimeout(() => { resolve("成功"); // 【异步执行】主线程空闲后才调用 resolve }, 0)
})
myPromise.then(res => {
console.log(res);
}, err => {
console.log(err);
})
这个时候,resolve没有立即调用,因此会先调用then,将任务放到队列里,等待resolve后执行handleCallback。
queueMicrotask保证了用户的回调不会阻塞同步代码的执行。
then中还有一个情况,就是then中什么也没有传,最后值还需要默认传递
js
const promise = new MyPromise((resolve, reject) => {
resolve("success");
});
promise
.then()
.then()
.then()
.then(
(value) => console.log(value),
(err) => console.log(err)
);
只需要加一个默认回调即可
js
onFulfilled =
typeof onFulfilled === "function" ? onFulfilled : (value) => value;
onRejected =
typeof onRejected === "function"
? onRejected
: (reason) => {
throw reason;
};
分析一下调用,一旦顶部的promise的resolve调用,不管是resolve同步还是异步的被调用了,都会导致handleCallback被调用,最终onFulfilled被调用,并将值返回。
对于上面的例子, 一共有p p1 p2 p3 p4
P的resolve后,p1是一个新的promise,内部调用(value) => value,resolve后返回值为value; p2进来后发现p1是resolve(value)的敲定状态,也调用(value) => value,将值传递下去。
还剩下一个核心函数resolvePromise,用来处理返回值不是普通值的情况。
用例1:
js
// 使用 thenable 对象
const myThenable = {
then: function (resolve, reject) {
setTimeout(() => {
resolve("success myThenable");
reject("fail myThenable");
}, 1000);
},
};
new MyPromise((resolve, reject) => {
resolve("success");
})
.then((value) => {
console.log(value);
return myThenable;
})
.then((value) => console.log(value));
这个用例中返回了一个对象,该对象有then方法,
用例2:
js
// 使用 thenable 对象
new MyPromise((resolve, reject) => {
resolve("success");
})
.then((value) => {
console.log(value);
return new MyPromise((resolve) => setTimeout(() => resolve(num + 5), 1000));;
})
.then((value) => console.log(value));
这个用例返回了一个Promise对象。
当然还有更复杂的,返回嵌套的情况,可以使用递归解决,直到遇到一个普通值再结束。
上面两个用例有点难以理解,先来看普通的Promise对象:
js
new Promise((resolve, reject) => {
// 这个函数就是 executor,它会被立即同步执行
setTimeout(() => {
resolve('done'); // 调用 resolve 改变 Promise 状态
}, 1000);
});
- 作用 :
executor负责启动异步操作,并在操作完成时调用resolve或reject来改变 Promise 的状态。 - 特点 :
executor是立即执行 的,并且由 Promise 构造函数传入resolve和reject两个函数。
再来看then方法:
js
promise.then(
value => console.log(value), // 成功回调
error => console.error(error) // 失败回调
);
- 作用:注册当 Promise 状态变为 fulfilled 或 rejected 时执行的回调。
- 特点 :
then方法不会主动调用resolve或reject,它只是注册监听。它返回一个新的 Promise,用于链式调用。
Thenable 对象中的 then 方法
js
const myThenable = {
then: function (resolve, reject) {
// 这个 then 方法类似于 executor
setTimeout(() => {
resolve("success myThenable"); // 主动调用 resolve
reject("fail myThenable"); // 也可以调用 reject
}, 1000);
},
};
-
作用 :当 Promise 机制(例如
Promise.resolve(myThenable))遇到 thenable 对象时,会自动调用其then方法 ,并传入两个回调(resolvePromise和rejectPromise的包装函数)。thenable 内部的then方法可以像 executor 一样启动异步操作,并在适当时候调用传入的resolve或reject来通知结果。 -
特点:
- 这个
then方法承担了启动异步操作并触发状态改变的责任,与 Promise 构造函数的 executor 角色一致。 - 它和 Promise 的
then方法名称相同,但语义完全不同 :前者是操作发起者 ,后者是结果监听者。
- 这个
其实写法上也可以看出来,自定义对象的then方法,相当于构造方法了,也是立即执行的,因为两者都叫 then,而且在 ES6 之前,许多 Promise 库(如 Q、Bluebird)的 thenable 对象确实用 then 方法来包装异步操作。但在原生 Promise 中,这两个角色被清晰地分开:
- 构造函数中的 executor:启动操作 + 触发完成。
- 原型上的
then方法:注册回调 + 返回新 Promise。
而 thenable 将"启动操作"和"接收回调"合并到了同一个 then 方法中。当 Promise 处理 thenable 时,它相当于把 thenable 的 then 方法当作一个 executor 来使用,传入的 resolve 和 reject 就是用来改变最终 Promise 状态的函数。
原生 Promise 流程:
new Promise(executor)→ executor 立即执行,启动异步任务。- 异步任务完成 → 调用
resolve(或reject)→ Promise 状态改变。 - 后续调用
then注册回调 → 回调会在状态改变后被调用。
Thenable 被 Promise 处理时的流程:
Promise.resolve(myThenable)→ 检测到 thenable。- Promise 内部调用
myThenable.then(onFulfilled, onRejected),其中onFulfilled和onRejected是 Promise 提供的包装函数。 myThenable.then方法内部可以启动异步操作,并在适当时调用onFulfilled(即传入的resolve函数)或onRejected(即传入的reject函数)。- 调用
onFulfilled或onRejected会最终改变由Promise.resolve返回的那个 Promise 的状态。
所以,myThenable.then 相当于 Promise 构造函数的 executor,而 Promise.resolve(myThenable) 相当于 new Promise(executor) 。
根据上面的分析,可以来写一下resolvePromise这个函数了,首先是MYPromise 实例:
js
if (x instanceof MYPromise) {
// 根据 x 的状态调用 resolve 或 reject
x.then(
y => {
resolvePromise(promise2, y, resolve, reject);
},
reason => {
reject(reason);
}
);
}
递归调用返回值的then方法,直到返回值是一个普通值,我们再resolve掉。
对于myThenable
js
// 获取 x 的 then 方法
const then = x.then;
if (typeof then === 'function') { // 如果 then 是函数
// 使用 x 作为上下文调用 then 方法
then.call(
x,
y => { // 成功回调
if (called) return; // 如果已经调用过,直接返回
called = true;
// 递归处理 y
resolvePromise(promise2, y, resolve, reject);
},
reason => { // 失败回调
if (called) return; // 如果已经调用过,直接返回
called = true;
reject(reason);
}
);
}
有几个需要注意的点,第一这里也是调用了then方法,并且写法上和原生的有点类似,都是传入了一个回调。为什么呢,上面说了myThenable的then有点像构造方法,接收的是resolve,Promise的then接收了onFulfilled回调,对于这俩回调,resolvePromise 在处理的时候都调用了resolvePromise。
resolve 与 onFulfilled 的区别
| 角色 | 来源 | 作用 | 被谁调用 |
|---|---|---|---|
resolve |
Promise 构造函数(executor 的第一个参数) |
将 Promise 状态从 pending 变为 fulfilled,并设置内部 value。 | 由用户(或异步任务完成时)主动调用。 |
onFulfilled |
then 方法的第一个参数 |
当 Promise 变为 fulfilled 时被自动调用,接收该 Promise 的 value 作为参数,用于处理结果。 | 由 Promise 内部机制在状态变更后调用。 |
简言之,resolve 是"写"操作(触发状态变更),onFulfilled 是"读"操作(响应状态变更) 。
当 Promise 引擎遇到一个 thenable 对象(如 myThenable)时,它会调用该对象的 then 方法,并传入两个包装函数:
js
then.call(x,
(y) => { /* 类似 resolve 的角色 */ },
(r) => { /* 类似 reject 的角色 */ }
);
这个第一个包装函数(通常记为 resolvePromise 的包装)确实在语义上类似于 executor 中的 resolve------它被 thenable 内部的异步操作调用,用来传递成功值。但区别在于:
- 它并不直接改变最终 Promise 的状态 ,而是先经过
resolvePromise的递归解析,最终才可能调用最外层的resolve。 - 它的任务是接收 thenable 产生的成功值
y,然后启动递归解析过程。
所以,在 thenable 处理中,我们传入的成功回调模拟了 resolve 的行为,但实际上是解析流程的起点。
resolve拿到值,处理then中回调,该回调返回不是普通值,递归处理该值。
变成了,resolve拿到值,该值不是普通值,递归处理该值。