10分针撸一个简单版的Promise

271 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

什么是Promise

一个 Promise 对象代表一个在这个 promise 被创建出来时不一定已知的值。它让您能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。 这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者。 — 引用自MDN文档

快速上手Promise

我们先回忆一下Promise的使用。Promise应该具备以下两个特征:

  1. 有一个执行函数executor传入
  2. 具备三种状态pending,fulfilled和rejected
  3. 当状态从pending转为fulfilled或者rejected,状态无法再改变

看一下下面简单的例子:

const promise = new Promise((resolve, reject) => {
  console.log(1)
  resolve()
  console.log(2)
})
​
promise.then(() => {
  console.log(3)
})

将这个结果放入浏览器的控制台上执行以下, 其执行结果是 1,2,3。

现在我们根据上面的例子来创建我们自己的Promise。从上面的例子中,我们发现Promise是一个构造函数,而且结构一个函数作为入参,还是一个then方法。我们先创建一个Promise的骨架:

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
​
function MyPromise(executor) {
  this.value = null // 记录异步任务执行成功的结果
  this.reason = null // 记录异步任务失败的结果
  this.state = PENDING // 异步任务的状态,当前为pending
  const that = this
  
  function resolve(value) {
    if (that.state === PENDING) {
      that.value = value
      that.state = FULFILLED
    }
  }
  
  function reject(reason) {
    if (that.state === PENDING) {
      that.reason = reason
      that.state = REJECTED
    }
  }
  // 将resolve和reject函数传给executor
  executor(resolve, reject)
}
// 创建then函数,并传入两个参数onFulfilled和onRejected(参数可选)
MyPromise.prototype.then = function(onFulfilled, onRejected) {
  if (this.state === FULFILLED) {
    onFulfilled(this.value)
  } else if (this.state === REJECTED) {
    onRejected(this.reason)
  }
}
​
​

我们来试一下自己写的代码,改造以下列子:

const promise = new MyPromise((resolve, reject) => {
  console.log(1)
  resolve()
  console.log(2)
})
​
promise.then(() => {
  console.log(3)
})

将我们的代码放入浏览器跑以下,输出结果如下:

image_f9UxVMTyqDDV6PiX472xRk.png

可以看出我们的初版的Promise成功了,但是还是有不完善的地方:

  1. 代码中没有体现出异步的地方
  2. then无法链式调用

现在我们继续完善,将我们的MyPromise支持异步和链式调用:

先支持异步的能力,JavaScript中我们可以通过setTimeout来模拟异步任务的,也可以通过queueMicrotask来模拟。

这里我们选用queueMicrotask来模拟,具体原因可以参考 queueMicrotask

// 先改造MyPromise构造函数
function MyPromise(executor) {
  this.value = null // 记录异步任务执行成功的结果
  this.reason = null // 记录异步任务失败的结果
  this.state = PENDING // 异步任务的状态,当前为pending
  const that = this
  // ------新增-------
  this.fulfilledQueue = [] // 存放resolve的回调函数
  this.rejectedQueue = [] // 存放reject的回调函数
  
  function resolve(value) {
    if (that.state === PENDING) {
      that.value = value
      that.state = FULFILLED
      // ------新增-------
      queueMicrotask(() => {
        that.fulfilledQueue.forEach(fn => fn())
      })
    }
  }
  
  function reject(reason) {
    if (that.state === PENDING) {
      that.reason = reason
      that.state = REJECTED
      // ------新增-------
      queueMicrotask(() => {
        that.rejectedQueue.forEach(fn => fn())
      })
    }
  }
  // 将resolve和reject函数传给executor
  executor(resolve, reject)
}
​

这里新增了fulfilledQueue 和rejectedQueue 两个队列,用来分别用来存放成功或失败时的回调函数。同时,再resolve或reject函数中,新增微任务的方式调用注册队列的函数。接下来我们改造以下then函数。除了返回this以外,我们需要把还处于pending状态的resolve和reject函数推入队列中:

MyPromise.prototype.then = function(onFulfilled, onRejected) {
  if (this.state === FULFILLED) {
    onFulfilled(this.value)
  } else if (this.state === REJECTED) {
    onRejected(this.reason)
  } else if (this.state === PENDING) {
    this.fulfilledQueue.push(onFulfilled)
    this.rejectedQueue.push(onRejected)
  }
  return this
}

改造完成。我们来试一下:

const p = new MyPromise((resolve, reject) => {
  resolve('成功了')
})
p.then(value => {
  console.log(value)
  console.log('第一个任务')
}).then(() => {
  console.log('第二个任务')
})

输出结果如下:

image_d2SgA1Qknh1A9sr3zLhUj2.png

可以看到,我们的链式调用成功了!

尽管我们实现了一个属于自己的Promise,但是这个Promise还是比较脆弱单薄的,如:resolve如果不是函数,应该怎么处理;这里我们应该怎么处理?这里预告一下,下一节我们来实现一个完整版的Promise,更加深入的理解Promise和Promise A+标准。

我们来总结Promise的几个特点:

  1. Promise具有三种状态:pending,fulfilled和rejected
  2. Promise一旦状态改变到fulfiled或者rejected时,状态是不可逆的(无法回到pending状态)
  3. Promise的then函数是微任务节点,且支持链式调用