深度解析Promise(二)

本文深入解析Promise的实现逻辑,包括状态变迁、then方法的微任务机制、链式调用及catch方法。通过模拟异步操作,阐述如何创建并管理Promise状态,确保回调函数在正确的时间执行。同时,探讨了thenable对象作为Promise通用性的概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

分析一下实现Promise需要哪些逻辑

  • Promise有三种状态,pending(进行中)、fulfilled(已完成)、reject(已失败),外界无法改变其状态,且一旦状态改变就不会再变了
  • 实例化一个 Promise 需要传入一个 executor 函数 ,业务代码在 executor 函数中执行,另外 executor 函数接收两个参数 resolve 和 reject。resolve 和 reject 是 Promise 构造函数的内置函数
new Promise((resolve,reject)=>{
	// do something
})
  • 在 executor 函数中业务代码执行成功了,调用 resolve 函数,把 Promise 的状态变为已成功,另外通过参数把业务代码的执行成功的结果传递到 Promise 中
  • 在 executor 函数中业务代码执行失败了,调用 reject 函数,把 Promise 的状态变为已失败,另外通过参数把业务代码的执行失败的原因传递到 Promise 中
  • 实例方法 then 的第一个参数是业务代码执行成功的回调函数,第二个参数是业务代码执行失败的回调函数,当业务代码执行完毕后,会根据执行结果调用对应的回调函数,且这些回调函数接收业务代码的执行结果作为参数
  • then方法可以链式调用,具有穿透性
  • 实例方法 catch 来添加业务代码执行失败的回调函数

那么下面就一一来实现 Promise 的功能

初步搭建

根据分析,首先我们需要完成这些功能:

  • Promise 构造函数接收 executor 函数作为参数,且在其中执行 executor 函数

  • Promise 构造函数中有 resolve 和 reject 内置方法,并作为参数传递给 executor 函数

  • 设置个实例属性 status 来存储状态

  • 内置函数 resolve 可以把状态变为已成功,内置函数 reject 可以把状态变为已失败,且一旦状态改变就不会再变

// 这里使用了Symbol是为了防止外界其他原因改变了状态
const Pending = Symbol('Pending');
const Fulfilled = Symbol('Fulfilled');
const Rejected= Symbol('Rejected');

function Promise(excutor){
	// 初始状态为pending
	this.status = Pending;
	const resolve = () => {  // 这里为了this的指向,使用了箭头函数,如果不用箭头函数,需要用变量存一下this
		if(this.status === Pending){  // 只有在状态为pending时才可以发生改变
			this.status = Fulfilled;
		}
	};
	const reject = () => {
		if(this.status === Pending){  // 只有在状态为pending时才可以发生改变
			this.status = Rejected;
		}
	};
	excutor(resolve,reject);
}

初步实现then方法

按照上面对 then 实例方法的业务场景的简单分析,在 then 实例方法中调用回调函数时,还要把 executor 函数中业务代码的执行结果作为参数传递进去,那么要新增实例属性来存储业务代码的执行结果。另外执行成功的结果通过内置方法 resolve 的参数传入,其执行失败的原因通过内置方法 reject 的参数传入

因为then方法是实例可调用的,所以then方法是在构造函数原型上的:

// 这里使用了Symbol是为了防止外界其他原因改变了状态
const Pending = Symbol('Pending');
const Fulfilled = Symbol('Fulfilled');
const Rejected= Symbol('Rejected');

function Promise(excutor){
	// 初始状态为pending
	this.status = Pending;
	this.value = undefined;
	this.error = undefined;
	const resolve = value => {  // 这里为了this的指向,使用了箭头函数,如果不用箭头函数,需要用变量存一下this
		if(this.status === Pending){  // 只有在状态为pending时才可以发生改变
			this.status = Fulfilled;
			this.value = value;
		}
	};
	const reject = value => {
		if(this.status === Pending){  // 只有在状态为pending时才可以发生改变
			this.status = Rejected;
			this.error = value;
		}
	};
	excutor(resolve,reject);
};

Promise.prototype.then = function(resolveCallback,rejectCallback){  // 这里为什么不用箭头函数,注意this指向
	if(this.status === Fulfilled){
		if(resolveCallback && typeof resolveCallback === 'function'){
			resolveCallback(this.value);
		}
	}
	if(this.status === Rejected){
		if(rejectCallback && typeof rejectCallback === 'function'){
			rejectCallback(this.error);
		}
	}
}

初步实现了then方法,那么我来验证一下:

const promise = new Promise((resolve,reject)=>{
	resolve('执行成功');
});

promise.then(res=>{
	console.log(res);
})

控制台打印结果
控制台能打印出我们想要的结果,说明到目前为止,我们写的都没问题,但是接下来我们思考,如果我们的excutor是个执行异步方法的函数呢:

const promise = new Promise((resolve,reject)=>{
	setTimeout(()=>{
		resolve('执行成功');
	},2000);
});

promise.then(res=>{
	console.log(res);
})

结果可以发现,2秒后并没有打印出我们想要的结果,因为调用 then 实例方法时,Promise 的状态是 Pending ,虽然2秒后 Promise 的状态变为 Fulfilled ,但是 then 实例方法已经调用过了

那么要怎么控制 then 实例方法中回调函数的执行时机。可以用发布订阅者的设计模式来实现。

当调用 then 实例方法时,如果 Promise 的状态是 Pending 时,先将成功回调函数和失败回调函数分别存放起来,在 executor 函数中异步任务执行结束,触发内置方法 resolve 或 reject,在其中去依次调用这些回调函数。

const Pending = Symbol('Pending');
const Fulfilled = Symbol('Fulfilled');
const Rejected= Symbol('Rejected');

function Promise(excutor){
	this.status = Pending;
	this.value = undefined;
	this.error = undefined;
	this.onFulfilled = [];  //这里用数组来存放resolve时的回调
	this.onRejected = []; //这里用数组来存放reject时的回调
	const resolve = value => {
		if(this.status === Pending){
			this.status = Fulfilled;
			this.value = value;
			this.onFulfilled.forEach(fn => fn());
		}
	};
	const reject = value => {
		if(this.status === Pending){
			this.status = Rejected;
			this.error = value;
			this.onRejected.forEach(fn => fn());
		}
	};
	excutor(resolve,reject);
};

Promise.prototype.then = function(resolveCallback,rejectCallback){
	if(this.status === Fulfilled){
		if(resolveCallback && typeof resolveCallback === 'function'){
			resolveCallback(this.value);
		}
	}
	if(this.status === Rejected){
		if(rejectCallback && typeof rejectCallback === 'function'){
			rejectCallback(this.error);
		}
	}
	//如果调用then方法的时候,状态还是pending,就先将回调保存起来
	if(this.status === Pending){
		if(resolveCallback && typeof resolveCallback === 'function'){
			this.onFulfilled.push(()=>{
				resolveCallback(this.value);
			});
		}
		if(rejectCallback && typeof rejectCallback === 'function'){
			this.onRejected.push(()=>{
				rejectCallback(this.error)
			});
		}
	}
}

再进行实例化验证一下:

const promise = new Promise((resolve,reject)=>{
	setTimeout(()=>{
		resolve('执行成功');
	},2000);
});

promise.then(res=>{
	console.log(res);
})

结果控制台在2秒后能够成功打印出“执行成功”,说明目前我们的逻辑都正确,再测试一下reject的情况:

const promise = new Promise((resolve,reject)=>{
	setTimeout(()=>{
		reject('执行失败');
	},2000);
});

promise.then(res=>{
	// do nothing
},err=>{
	console.log(err)
})

控制台在2秒后能够成功打印出“执行失败”

then方法的微任务机制

由于原生的 Promise 是V8引擎提供的微任务,我们无法还原V8引擎的实现,所以这里使用 setTimeout 模拟异步,所以原生的是微任务,这里是宏任务代替(如果你想实现 promise 的微任务,可以 mutationObserver 替代 seiTimeout 来实现微任务。这里只是模拟异步而已)

Promise.prototype.then = function(resolveCallback,rejectCallback){  
	if(this.status === Fulfilled){
		if(resolveCallback && typeof resolveCallback === 'function'){
			// 用setTimeout代替微任务
			setTimeout(()=>{
				resolveCallback(this.value);
			},0);
		}
	}
	if(this.status === Rejected){
		if(rejectCallback && typeof rejectCallback === 'function'){
			setTimeout(()=>{
				rejectCallback(this.error);
			},0);
		}
	}
	if(this.status === Pending){
		if(resolveCallback && typeof resolveCallback === 'function'){
			this.onFulfilled.push(()=>{
				setTimeout(()=>{
					resolveCallback(this.value);
				},0);
			});
		}
		if(rejectCallback && typeof rejectCallback === 'function'){
			this.onRejected.push(()=>{
				setTimeout(()=>{
					rejectCallback(this.error);
				},0);
			});
		}
	}
}

then的链式调用

实例方法 then 链式调用有两个要求:

  • 在实例方法 then 后面可以直接使用实例方法 then
  • 在前面一个实例方法 then 返回一个值,不管是什么值,在后面一个实例方法 then 中都能获取到

既然要能链式调用,那么then中返回的肯定也是一个promise,把实例方法 then 返回的值 value,通过 resolve(value) 或 reject(value) 传递出去

当然我们还需要一个专门处理then的函数,用来解决对实例方法then返回的值的类型做判断和对应处理:

Promise.prototype.then = function(resolveCallback,rejectCallback){
	let promise = new Promise((resolve,reject)=>{
		if(this.status === Fulfilled){
			if(resolveCallback && typeof resolveCallback === 'function'){
				setTimeout(()=>{
					let x = resolveCallback(this.value);
					handleValue(promise,x,resolve,reject);   // 这里用工具函数处理
				},0);
			}
		}
		if(this.status === Rejected){
			if(rejectCallback && typeof rejectCallback === 'function'){
				setTimeout(()=>{
					let x = rejectCallback(this.error);
					handleValue(promise,x,resolve,reject);
				},0);
			}
		}
		if(this.status === Pending){
			if(resolveCallback && typeof resolveCallback === 'function'){
				this.onFulfilled.push(()=>{
					setTimeout(()=>{
						let x = resolveCallback(this.value);
						handleValue(promise,x,resolve,reject);
					},0);
				});
			}
			if(rejectCallback && typeof rejectCallback === 'function'){
				this.onRejected.push(()=>{
					setTimeout(()=>{
						let x = rejectCallback(this.error);
						handleValue(promise,x,resolve,reject);
					},0);
				});
			}
		}
	})
	return promise
}

然后写一下这个handleValue工具函数:

const handleValue = (promise,x,resolve,reject)=>{
	// 如果自己循环调用自己
	if(promise === x){
		return reject(new TypeError('链式循环调用了'));
	}
	let once = false;   // 确保只传递出去一次值
	if(typeof x === 'object' && x !== null || typeof x === 'function'){
		const then = x.then;
		// 判断x是不是Promise
		if(then && typeof then === 'function'){
			//调用then实例方法处理Promise执行结果
			then.call(x,y=>{
				if(once) return
				once = true;  // 防止Promise中Promise执行成功后又传递一个Promise过来,
				handleValue(promise,y,resolve,reject);   // 递归解析
			},r=>{
				if(once) return
				once = true;
				reject(r);
			})
		}else{
			// 如果x是个普通对象,直接调用resolve(x)
			resolve(x);
		}
	}else{
		// 如果x是个原始值,直接调用resolve(x)
		resolve(x);
	}
}

在上述代码中,判断typeof then === 'function’时其实是在判断返回的 x 是否为一个 Promise。如果没有 then 函数,x 即为普通值,直接返回 resolve(x)。如果有 then 函数,x 即为一个 Promise,就递归解析这个 Promise,直到 x 是一个普通值后作为最后的结果返回

那么为什么用typeof then === ‘function’ 判断 x 是否为一个 Promise ,而不是用 x instanceof Promise 。 这是为了让 Promise 更具有通用性,所以一个 thenable 对象也可以看做是一个 Promise 。 thenable 对象就是一个拥有 then 方法的对象,如下代码所示例:

let thenable = {
    then: function(resolve, reject){
    	resolve('执行成功')
    }
}

在 thenable.then 方法中通过 resolve 传递执行成功的结果。但是 thenable 对象不是通过 Promise 类 new 出来的,故不能通过 x instanceof Promise 来判断是不是一个 Promise

then的穿透

如果要实现如下的逻辑呢?

const p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('"执行成功"')
    }, 2000)

})
p.then().then(res =>{
    console.log(res)
})

此时的then方法是需要具备穿透性的,后面的实例方法 then 依旧可以得到之前实例方法 then 返回的值,我们再来修改一下:

Promise.prototype.then = function(resolveCallback,rejectCallback){
	resolveCallback = typeof resolveCallback === 'function' ? resolveCallback : v => v;
	rejectCallback = typeof rejectCallback ===  'function'  ? rejectCallback : err => {
		throw err
	};
	let promise = new Promise((resolve,reject)=>{
		if(this.status === Fulfilled){
			if(resolveCallback && typeof resolveCallback === 'function'){
				setTimeout(()=>{
					let x = resolveCallback(this.value);
					handleValue(promise,x,resolve,reject); 
				},0);
			}
		}
		if(this.status === Rejected){
			if(rejectCallback && typeof rejectCallback === 'function'){
				setTimeout(()=>{
					let x = rejectCallback(this.error);
					handleValue(promise,x,resolve,reject);
				},0);
			}
		}
		if(this.status === Pending){
			if(resolveCallback && typeof resolveCallback === 'function'){
				this.onFulfilled.push(()=>{
					setTimeout(()=>{
						let x = resolveCallback(this.value);
						handleValue(promise,x,resolve,reject);
					},0);
				});
			}
			if(rejectCallback && typeof rejectCallback === 'function'){
				this.onRejected.push(()=>{
					setTimeout(()=>{
						let x = rejectCallback(this.error);
						handleValue(promise,x,resolve,reject);
					},0);
				});
			}
		}
	})
	return promise
}

catch方法

catch其实就是 then(null, rejectCallback)的别名

Promise.prototype.catch =  function(rejectCallback){
	this.then(null,rejectCallback);
}

优化完整代码

处理一下内部错误等

const Fulfilled = Symbol('Fulfilled')
const Rejected = Symbol('Rejected')
const Pending = Symbol('Pending')

const handleValue = (promise,x,resolve,reject) => {
  if(promise === x){
    return reject(new TypeError('检测到循环调用'))
  }
  let once = false
  if(typeof x === 'object' && x !== null || typeof x === 'function'){
    const then = x.then
    if(typeof then === 'function'){
      then.call(x,y=>{
        if(once) return
        once = true
        handleValue(promise,y,resolve,reject)
      },r=>{
        if(once) return
        once = true
        reject(r)
      })
    }else{
      resolve(x)
    }
  }else{
    resolve(x)
  }
}

function Promise(excutor){
  this.status = Pending
  this.value = undefined
  this.reason = undefined
  this.onFulfilled = []
  this.onRejected = []

  const resolve = value => {
    if(this.status === Pending){
      this.status = Fulfilled
      this.value = value
      this.onFulfilled.forEach(fn => {
        if(typeof fn === 'function'){
          fn()
        }
      })
    }
  }

  const reject = value => {
    if(this.status === Pending){
      this.status = Rejected
      this.reason = value
      this.onRejected.forEach(fn => {
        if(typeof fn === 'function'){
          fn()
        }
      })
    }
  }

  try{
    excutor(resolve,reject)
  }
  catch(err){
    reject(err)
  }
}

Promise.prototype.then = function(fulfilledCallback,rejectedCallback){
  fulfilledCallback = typeof fulfilledCallback === 'function' ? fulfilledCallback : v => v
  rejectedCallback = typeof rejectedCallback === 'function' ? rejectedCallback : err => {
    throw err
  }
  let promise = new Promise((resolve,reject)=>{
    if(this.status === Pending){
      if(fulfilledCallback && typeof fulfilledCallback === 'function'){
        this.onFulfilled.push(()=>{
          setTimeout(()=>{
            try{
              let x = fulfilledCallback(this.value)
              handleValue(promise,x,resolve,reject)
            }catch(err){
              reject(err)
            }
          },0)
        })
      }
      if(rejectedCallback && typeof rejectedCallback === 'function'){
        this.onRejected.push(()=>{
          setTimeout(()=>{
            try{
              let x = rejectedCallback(this.reason)
              handleValue(promise,x,resolve,reject)
            }catch(err){
              reject(err)
            }
          })
        })
      }
    }

    if(this.status === Fulfilled){
      if(fulfilledCallback && typeof fulfilledCallback === 'function'){
        setTimeout(()=>{
          try{
            let x = fulfilledCallback(this.value)
            handleValue(promise,x,resolve,reject)
          }catch(err){
            reject(err)
          }
        },0)
      }
    }

    if(this.status === Rejected){
      if(rejectedCallback && typeof rejectedCallback === 'function'){
        setTimeout(()=>{
          try{
            let x = rejectedCallback(this.reason)
            handleValue(promise,x,resolve,reject)
          }catch(err){
            reject(err)
          }
        })
      }
    }
  }) 
  return promise
}

Promise.prototype.catch = function(rejectCallback){
  this.then(null,rejectCallback)
}

那么对Promise和他的实例方法then和catch的解析,我们就到这了,接下来我们会对他的静态方法再做具体的分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值