闲着无聊,写个promise

1,012 阅读3分钟

一、前言

  不折腾不前端,在现公司待了3年了,每一段时间都会去不同的公司面试,目的呢一是为了看下市场的动态,对前端有什么要求。二是为了测量自己在前端市场上能值多少钱。三是为了巩固知识,提升面试技巧。
  当然,不能挑那些你想要进的公司去做实验,像福厂、猪场等,他们会有你面试记录,影响之后的面试,得不偿失。
  最近在面试过程中,被问到了一个老生常谈的题目,promise的实现原理。所以决定写一篇关于promise源码实现的文章。

二、promsie使用方式

  正常在实现某个api源码的时候,首先是能熟练使用这个api,从api上考虑如何去实现这个源码,这是我一直以来的方式。

new MyPromise((resolve, reject) => {
	
}).then((data) => {
	// do something
}).then((data) => {
	// do something
}).catch((err) => {
	// do something
})

三、源码实现

  非常简单的一个例子,可以看出promise是一个构造函数,有几个方法,resolve, reject, then,catch,resolve和reject改变状态,根据状态执行then或者catch,所以可以马上就写出promise的接结构

Class Promise {
   constructor (executor) {
      this.status = 'pending' // pending fulfilled reject, 初始状态pending
      this.value = null // resolve结果
      this.reason = null // reject原因
      // 专门存放成功的回调函数
      this.onResolvedCallbacks = [];
      // 专门存放成功的回调函数
      this.onRejectedCallbacks = [];
      let resolve = (value) => {
	if (this.status !== 'pending') {
          return
        }
        this.status = 'fulfilled'
        this.value = value
        this.onResolvedCallbacks.forEach(fn => fn(this.value))
      }
      let reject = (reason) => {
	if (this.status !== 'pending') {
          return
        }
        this.status = 'reject'
        this.reason = reason
        this.onResolvedCallbacks.forEach(fn => fn(this.value))
      }
      try {
          executor(resolve, reject) // 同步执行
      } catch(err) {
          reject()
      }
  }
  then (onfulfilled, onrejected) {
  	// 使用setTimeout模拟异步
    if (this.status === 'pending') {
    	this.onResolvedCallbacks.push(() => {
            setTimeout(() => {
              onfulfilled(this.value)
            }, 0)
        })
    	this.onRejectedCallbacks.push(() => {
            setTimeout(() => {
              onrejected(this.value)
            }, 0)
        })
    } else if (this.status === 'fulfilled') {
    	setTimeout(() => {
          onfulfilled(this.value)
        }, 0)
    } else {
    	setTimeout(() => {
          onrejected(this.value)
        }, 0)
    }
  }
  catch (errHander) {
    this.then(null, errHander)
  }
}

  一个简易的promise就完成了,但是运行时发现并不能链式调用,原因就在于then的返回值不是一个promise,没有then方法,所以报错。所以处理方法就是返回一个新的promise,那么有一个问题,为什么是新的promise而不是return this?

四、链式调用

  因为为了保证逻辑的简单性,promise的状态只能从pending变为fulfilled或者reject,这也是promise名字的由来。如果使用return this,且then回调函数执行报错,状态还是fulfilled,promise是无法catch到错误。 那么我们改一下代码

then (onfulfilled, onrejected) {
  let newPromise = new MyPromise((resolve, reject) => {
    if (this.status === 'pending') {
      this.onResolvedCallbacks.push(() => {
        setTimeout(() => {
          onfulfilled(this.value)
        }, 0)
      })
      this.onRejectedCallbacks.push(() => {
        setTimeout(() => {
          onrejected(this.reason)
        }, 0)
      })
    } else if (this.status === 'fulfilled') {
      setTimeout(() => {
        let value = onfulfilled(this.value)
      	reject(value)
      }, 0)
    } else {
      setTimeout(() => {
        let reason = onrejected(this.reason)
      	reject(reason)
      }, 0)
    }
  })
  return newPromise
}

  至此,一个简易的promise就实现了,包含了then,catch方法,面试够用。

五、优化

  细心的同学可能已经发现,如果onfulfilled如果返回的是一个promise呢?我们不希望then拿到的结果是一个promise,那么就要等这个promise resolve时才能继续执行then方法。所以我们再改造一下

// 添加一个函数
function resolvePromise(promise, x, resolve, reject) {
  if ((typeof x === 'object' && x != null && x._proto_.contructor === MyPromse.contructor)) {
  // 此时就认为它是一个promise
    let then = x.then
    then.call(x, y => { // 从then中能拿到该x的结果,再resolve出去
      resolvePromsie(promise, y, resolve, reject) // 一直递归
    }, r => {
        reject(r);
    })
  } else if(typeof x === 'function') {
    let value = x()
    resolvePromise(promise, value, resolve, reject)
  } else {
    resolve(x)
  }
}
// 改造下then方法
then (onfulfilled, onrejected) {
  let newPromise = new MyPromise((resolve, reject) => {
    if (this.status === 'pending') {
      this.onResolvedCallbacks.push(() => {
        setTimeout(() => {
          let x = onfulfilled(this.value)
          resolvePromsie(newPromse, x, resolve, reject)
        }, 0)
      })
      this.onRejectedCallbacks.push(() => {
        setTimeout(() => {
          let x = onrejected(this.reason)
          resolvePromsie(newPromse, x, resolve, reject)
        }, 0)
      })
    } else if (this.status === 'fulfilled') {
      setTimeout(() => {
        let value = onfulfilled(this.value)
      	resolvePromsie(newPromse, value, resolve, reject)
      }, 0)
    } else {
      setTimeout(() => {
        let reason = onrejected(this.reason)
      	resolvePromsie(newPromse, reason, resolve, reject)
      }, 0)
    }
  })
  return newPromise
}

六、总结

  大多数时候,我们再日常工作中是不用去写promise源码,但是为了不被外界看成“熟练的api打工人”,还是撸一撸源码,其次也是为了promise的设计思路,提升编程能力。更重要的是基本每个面试官都会问,虽然不知道他们自己会不会,但是准备着总是没错的。