本文详细解析了JavaScript中Promise对象的核心机制,包括pending、fulfilled和rejected三种状态及其不可逆性。通过手写代码演示了如何使用class类封装Promise构造函数,处理resolve和reject函数的this指向问题,并实现了包含错误捕获的基础版本。进一步深入探讨了then方法的链式调用原理、微任务执行机制以及异步回调的队列管理,最终完成一个符合规范的简易Promise实现。
前言
要实现一个简易的Promise对象,首先需要掌握几个关键知识点。
Promise对象具有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果能够决定当前处于哪种状态,其他任何操作都无法改变这一状态。这也正是Promise一词的由来,其英文含义为“承诺”,象征着状态的不可逆性。
Promise构造函数接收一个执行函数作为参数,该函数内部包含两个由JavaScript引擎提供的回调函数:resolve和reject。reslove函数的作用是将Promise对象的状态从'pending'转换为'resolved'(即'fulfilled')状态,这与参数名相对应;而reject函数的作用则是将状态从'pending'转换为'rejected'状态。
需要注意的是,Promise对象的状态一旦改变,便不再发生变化。也就是说,当状态从pending变为resolved,或者从pending变为rejected后,无论后续发生什么,Promise的状态都将保持固定。
Promise的基础结构与用法
let p1 = new Promise((resolve, reject) => {
resolve('成功');
reject('失败');
throw('报错'); //相当于reject()
})
console.log(p1);
接下来,我们观察这三种状态在打印时的具体表现。



使用class类来实现Promise对象。
class myPromise {
constructor(executor) {
this.status = 'pending'; // 变更promise的状态
this.value = null;
executor(this.resolve, this.reject); // new 一个myPromise 得到的实例对象里面有两个函数
}
resolve(value) {
if (this.status !== 'pending') return;
this.status = 'fulfilled'; // 变更promise的状态
this.value = value;
}
reject(reason) {
if (this.status !== 'pending') return
this.status = 'rejected';
this.value = reason;
}
}乍看之下,这种代码逻辑似乎合理,让我们看看实际运行效果如何。

观察上述结果,不难发现resolve和reject两个方法的this指向存在问题。仔细分析可知,这两个方法作为实参传入构造函数时,this指向的是构造器函数本身,而非我们定义的myPromise构造函数。解决这一问题的方法很简单,只需使用bind方法显式绑定this的指向即可。
executor(this.resolve.bind(this), this.reject.bind(this))

此外,myPromise的状态一旦变更便不再改变,这已经具备原装Promise的一些特征。但在标准Promise中还存在错误捕获机制:只要Promise内部执行的逻辑抛出错误,就需要执行reject逻辑并将错误抛出。为此,我们可以使用try...catch语句来实现这一功能。
try {
executor(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error)
}至此,我们实现了一个极简版的Promise对象。然而,在实际开发中,我们通常使用Promise来处理异步问题。提到异步,就不得不提及Promise.prototype.then()方法。then方法返回的是一个新的Promise实例(注意,并非原来的Promise实例)。因此,我们可以采用链式写法,即在then方法后面继续调用另一个then方法。
then方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,这两个参数均为可选。
当Promise的状态为'fulfilled'时,执行第一个回调函数;当状态为'rejected'时,执行第二个回调函数。
必须等待Promise的状态发生一次变更(变为'fulfilled'或'rejected')后,才会执行then内部的逻辑。
.then支持链式调用,下一次.then的执行结果受上一次.then执行结果的影响。
了解以上几点后,我们就可以尝试实现.then方法了。
class myPromise {
constructor(executor) {
this.status = 'pending';
this.value = null;
this.onFulfilledCallbacks = []; // 用来保存成功的回调(处理异步)
this.onRejectedCallbacks = []; // 用来保存失败的回调(处理异步)
try {
executor(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error)
}
}
resolve(value) {
if (this.status !== 'pending') return;
this.status = 'fulfilled';
this.value = value;
// 调用then里面的回调
while (this.onFulfilledCallbacks.length) { // 当异步成功回调数组中存在回调函数,那就执行
this.onFulfilledCallbacks.shift()(this.value)
}
}
reject(reason) {
if (this.status !== 'pending') return
this.status = 'rejected';
this.value = reason;
while (this.onRejectedCallbacks.length) { // 当异步失败回调数组中存在回调函数,那就执行
this.onRejectedCallbacks.shift()(this.value)
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val // 判断.then的第一个参数是不是一个函数,如果不是就直接作为结果返回
onRejected = typeof onRejected === 'function' ? onRejected : val => { throw val } // 判断.then的第二个参数是不是一个函数,如果不是就直接作为错误返回
var thenPromise = new myPromise((resolve, reject) => { // 因为.then返回的是一个心的Promise对象
const resolvePromise = callback => { // 用于判断回调函数的类型
setTimeout(() => { // 让整个回调函数比同步代码晚一点执行,官方不是使用setTimeout实现
try {
const x = callback(this.value);
if (x === thenPromise) { // 你正在返回自身
throw new Error('不允许返回自身!');
}
if (x instanceof myPromise) { // 返回的是一个Promise对象
x.then(resolve, reject);
} else { // 直接返回一个值,作为resolve的值,传递给下一个.then
resolve(x);
}
} catch (error) {
reject(error);
throw new Error(error)
}
})
}
if (this.status === 'fulfilled') {
resolvePromise(onFulfilled)
} else if (this.status === 'rejected') {
resolvePromise(onRejected)
} else if (this.status === 'pending') {
this.onFulfilledCallbacks.push(resolvePromise.bind(this, onFulfilled));
this.onRejectedCallbacks.push(resolvePromise.bind(this, onRejected));
}
})
return thenPromise
}
}

写在最后
最后,与大家分享我是如何一步步实现这个简易版Promise的。
首先,从Promise构造函数的特点入手,包括三种状态以及状态一经改变便不再变化的特性。因此,在resolve和reject方法中加入判断逻辑:如果当前状态不是'pending',则直接return。这样就实现了状态一旦改变便不再变化的功能。因为.then中回调的执行取决于Promise的状态,当状态为'fulfilled'时执行.then的第一个回调函数,当状态为'rejected'时执行.then的第二个回调函数。但是,如果在Promise内部,在resolve或reject的外部包裹setTimeout,状态变更将会被加入到下一次宏任务队列中。为此,我们需要维护两个数组来存放未执行的回调函数。当状态改变后,在对应的resolve和reject方法中判断维护的未执行回调函数数组中是否有待执行的回调,如果有则直接调用。此外,由于.then返回的是一个Promise对象,我们不能直接将'onFulfilled'或'onRejected'其中一个回调返回出去,否则.then后面无法再链接.then。因此,在then方法中我们定义了一个resolvePromise函数,其目的是在返回的'onFulfilled'或'onRejected'外面包裹一层Promise对象,使其能够继续链接.then的回调。在这个resolvePromise函数内部,我们还添加了判断回调类型的逻辑:在官方定义的Promise对象中,规定回调不能是原Promise对象;另外两个判断分别是回调是否为Promise对象,以及如果不是Promise对象,则直接resolve()出去。
最后,同步代码会优先于.then的执行。因为.then是异步代码中的微任务,只有当宏任务执行完毕后,微任务才会执行。因此,我们在resolvePromise的回调外面包裹一层setTimeout,这样返回出去的.then的逻辑将会进入下一次的宏任务队列,从而实现了.then的执行比同步代码稍晚一些的效果。需要注意的是,官方实现并非使用setTimeout来实现这一机制。
到此,这篇关于详解JavaScript如何实现一个简易Promise对象的文章就介绍到这了。更多相关JavaScript实现Promise对象的内容,请搜索本站以前的文章或继续浏览下面的相关文章。希望大家以后多多支持本站!