手写一个自己的Promise(一)

手写一个自己的Promise

根据PromiseA+规范,自己手动实现一个Promise



前言

前几天面试,面试官要求手写一个Promise,可这玩意只会用不会写啊,那次面试结果自然就可想而知了,面试结束后必须痛定思痛,将这块难啃的骨头啃下来,同样的招式不能对圣斗士使用两次!因此查了一些资料,这里实现Promise的思路是借鉴了b站上渡一的关于Promise的课程思路,对此表示感谢,好了,废话不多说,我们进入正题。


一、什么是Promise?

ECMAscript 6 原生提供了 Promise 对象。
Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。
Promise 对象有以下两个特点:

1、对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:
pending: 初始状态,不是成功或失败状态。
fulfilled: 意味着操作成功完成。
rejected: 意味着操作失败。
只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是「承诺」,表示其他手段无法改变。
2、一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

二、开始手动实现

1.实现Promise的构造器部分

思路:
首先,创建一个MyPromise的类,在类中的constructor内接收一个执行器executor,这个执行器是一个函数,需要将这个执行器执行,这个执行器有点两个参数,分别是resolve方法和reject方法,因此一并定义出来。而resolve与reject执行,会将promise的状态从pending转变为fulfilled或reject,并将传入的参数作为当前promise返回结果。另外,如果promise的状态一旦改变,之后就不会再发生变化了,因此还需要额外加一层判断。
根据上述思路可以完成下面的代码

class MyPromise{

	#state='pending'  // 当前promise的状态,前面加#表示的是私有属性,内部可以随意访问,外部无法访问和修改
	#result=undefined  // 当前返回的结果
    constructor(executor) {
        const resolve = (data) => {
        	if(#state!=='pending') return // 判断如果状态发生了变化,停止后续代码的执行,防止状态和结果再次修改
        	this.#state = 'fufilled'
        	this.#result = data
        }
        const reject = (reason) => {
        	if(#state!=='pending') return
        	this.#state = 'rejected'
        	this.#result = reason
        }
        executor(resolve, reject)
        // executor(this.resolve.bind(this), this.reject.bind(this))
    }

	//resolve(){},
	//reject(){}
}

const p = new MyPromise((resolve,reject)=>{
	resolve(1)
})

ps:resolve和reject定义在构造函数内部,会在每次new一个MyPromise时会创建两个函数,而如果把这两个函数的创建放在原型链上,即代码中的注释部分,看似能避免每次new就创建函数,实际上会出现this指向问题(在外部调用时,像下面的resolve(1),此时如果是浏览器,this会指向window;如果是node环境,this会指向global),为了解决产生的this指向问题还需要用bind方法提前绑定this,而bind方法同样会创建一个新的function(如代码中注释部分),因此写着原型链上并不能避免每次new都创建新函数的问题,为方便,还是直接将resolve与reject写在构造函数内部。

代码写到这里,发现了两个可以优化的点

  • resolve和reject方法中的内容高度相似,可以抽离成一个方法
  • 状态直接写成字符串在JS中被称为“硬编码”,后续如果出现更改全局的字符串都需要更改,会很麻烦,因此可以考虑换成常量

根据上面两条优化,可以将代码变成下面这样

// 将状态字符串变为常量
const PENDING = 'pending'
const FUFILLED ='fulfilled'
const REJECTED = 'rejected'

class MyPromise{

	#state='pending'  // 当前promise的状态,前面加#表示的是私有属性,内部可以随意访问,外部无法访问和修改
	#result=undefined  // 当前返回的结果

	// 提出一个公共方法,专门修改状态和结果
	#changeState(state, result){
		if(this.#state!=='pending') return
		this.#state = state
        this.#result = result
	}
    constructor(executor) {
        const resolve = (data) => {
        	this.#changeState(RESOLVED,data)
        }
        const reject = (reason) => {
        	this.#changeState(REJECTED, reason)
        }
        executor(resolve, reject)
}

接下来则是执行器完成执行,但如果执行器在运行时报错,根据promise目前实现的情况,就是将promise的执行状态改为rejected,同时将报错原因作为结果保存。
因此可以用try…catch包裹住执行器,并在catch中执行reject,并把error作为结果保存,由此可以完成下述代码

// 将状态字符串变为常量
const PENDING = 'pending'
const FUFILLED ='fulfilled'
const REJECTED = 'rejected'

class MyPromise{

	#state='pending'  // 当前promise的状态,前面加#表示的是私有属性,内部可以随意访问,外部无法访问和修改
	#result=undefined  // 当前返回的结果

	// 提出一个公共方法,专门修改状态和结果
	#changeState(state, result){
		if(this.#state!=='pending') return
		this.#state = state
        this.#result = result
	}
    constructor(executor) {
        const resolve = (data) => {
        	this.#changeState(RESOLVED,data)
        }
        const reject = (reason) => {
        	this.#changeState(REJECTED, reason)
        }
        try{
			executor(resolve, reject)
		}catch(error){
			reject(error)
		}
}

const p = new MyPromise((resolve,reject)=>{
	throw 123
})

// const p = new MyPromise((resolve, reject) => {
//     setTimeout(() => {
//         throw 123
//     })
// })

至此,MyPromise的构造函数基本完成,接下来也就是Promise A+的重中之重,即then方法的实现,就放在下一篇文章里了。

这里是细心的小伙伴可能会发现,try…catch确实能捕获到同步报错,但对于异步报错还是没办法捕获到啊,捕获不到就不会修改状态和结果,那这里还是有问题(就是下面注释的部分,运行或打印结果会发现状态并没有改变,还是pending,结果也无法记录),但实际上包括官方实现的Promise对于这种异步报错同样无法解决,因此大家一定要记得这一点:Promise只对同步错误有回应,而异步错误是不会影响Promise的状态和结果的!!!


总结

MyPromise的构造器部分代码实现思路

  1. 在构造函数中接收一个executor执行器执行,该执行器有两个参数,分别是resolve成功的回调和reject失败的回调
  2. 设置三个常量保存MyPromise的三种状态,设置一个#result用来保存promise的结果,提供一个用于修改MyPromise状态和结果的方法changeState
  3. 在resolve与reject方法中执行changeState,并把对应的状态和结果作为参数传入
  4. 最后通过try…catch判断执行器executor执行过程中是否报错,如果报错则在catch中执行reject并传入error作为reject的原因

最后,文章中部分资料的引入如下
JavaScript Promise 对象 | 菜鸟教程
PromiseA+官网

  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值