一步步实现promise
一步步实现promise

一步步实现promise

promise解决什么问题:

  • 成功的回调和失败的回调只会调用其中一个,而且只会调用一次
  • 回调必须是异步调用的
  • 回调之间支持链式调用

前置知识:

先看看new Promise接收什么参数,接受一个executor函数,这个executor会接受两个函数参数,这两个函数的调用会影响Promise内部的状态

new <unknown>(executor: (resolve: (value: unknown) => void, reject: (reason?: any) => void) => void) => Promise<unknown>

再看看Promise.then(),接受两个函数参数,一个负责成功的回调一个负责错误的回调

Promise<unknown>.then<unknown, never>(onfulfilled?: ((value: unknown) => unknown) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<unknown>

那我们先从一般回调开始,回调是一种控制反转,函数如何调用什么时候调用将由内部函数决定

let result
function normalCb(cb) {
  cb(result)
}
result = 'success'
normalCb(console.log)

但是我们这里只能监听到成功,错误的回调也需要处理

let result
let error
const PENDING = 0;
const FULFILLED = 1;
const REJECTED = 2;
let state = PENDING
function normalErrorCb(cb, errorCb) {
  switch(state) {
    case FULFILLED: {
      return cb(result)
    }
    case REJECTED: {
      return errorCb(error)
    }
    default: {
      console.log('pending...')
    }
  }
}
function setStatus(status, param) {
  switch(status) {
    case FULFILLED: {
      state = FULFILLED
      result = param
      break
    }
    case REJECTED: {
      state = REJECTED
      error = param
      break
    }
    default: {
      console.log('pending...')
    }
  }
}

setStatus(FULFILLED, 'success')
normalErrorCb(console.log, console.error)

这样我们既能监听到成功和错误,但其实状态只应该改变一次,只会调用成功或错误,而且可以多次监听函数

//  状态只能改变一次,异步调用,并能接受多个回调
let result
let error
const PENDING = 0
const FULFILLED = 1
const REJECTED = 2
let state = PENDING
let done = false
const handlers = []
function normalErrorCb (cb, errorCb) {
  setTimeout(() => {
    switch (state) {
      case FULFILLED: {
        return cb(result)
      }
      case REJECTED: {
        return errorCb(error)
      }
      case PENDING: {
        return handlers.push({ cb, errorCb })
      }
      default: {
        console.log('wrong status')
      }
    }
  }, 0)
}

function setStatus (status, param) {
  if (done) return
  switch (status) {
    case FULFILLED: {
      done = true
      state = FULFILLED
      result = param
      handlers.forEach((handler) => handler.cb(result))
      break
    }
    case REJECTED: {
      done = true
      state = REJECTED
      error = param
      handlers.forEach((handler) => handler.errorCb(error))
      break
    }
    default: {
      console.log('pending...')
    }
  }
}

normalErrorCb(console.log, console.error)
setStatus(FULFILLED, 'success')
setStatus(REJECTED, 'error')
normalErrorCb(console.log.bind(undefined, 'one more'), console.error)

现在我们的函数状态只会改变一次,而且能多次监听,但是现在全局状态都是变量,我们要把他用函数包起来

//  我们声明了很多东西在全局作用域,我们应该把他包起来
function MockPromise (fn) {
  let result
  let error
  const PENDING = 0
  const FULFILLED = 1
  const REJECTED = 2
  let state = PENDING
  let done = false
  const handlers = []
  this.then = function normalErrorCb (cb, errorCb) {
    setTimeout(() => {
      switch (state) {
        case FULFILLED: {
          return cb(result)
        }
        case REJECTED: {
          return errorCb(error)
        }
        case PENDING: {
          return handlers.push({ cb, errorCb })
        }
        default: {
          console.log('wrong status')
        }
      }
    }, 0)
  }

  function setStatus (status, param) {
    if (done) return
    switch (status) {
      case FULFILLED: {
        done = true
        state = FULFILLED
        result = param
        handlers.forEach((handler) => handler.cb(result))
        break
      }
      case REJECTED: {
        done = true
        state = REJECTED
        error = param
        handlers.forEach((handler) => handler.errorCb(error))
        break
      }
      default: {
        console.log('pending...')
      }
    }
  }

  fn(resolve, reject)

  function resolve (successValue) {
    setStatus(FULFILLED, successValue)
  }
  function reject (error) {
    setStatus(REJECTED, error)
  }
  // 怎么暴露这个方法呢?一方面可以像then一样直接暴露,但是更好的方法是,通过构造函数的时候暴露出去,这样resolve,reject不会被外界改变
  // this.resolve = function (successValue) {
  //   setStatus(FULFILLED, successValue)
  // }
  // this.reject = function (error) {
  //   setStatus(REJECTED, error)
  // }
}

const p = new MockPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
    reject('error')
  }, 500)
})
p.then(console.log, console.error)

现在跟我们的promise已经很像了,但是无法做到链式调用,实现链式调用的时候还需要考虑,如果再then里面也调用了一个promise,那这个新返回的promise应该是加在这个promise的回调,只有这个promise成功执行后,新的promise才决议状态,所以resolve还有可能会得到一个promise

//  实现链式调用
function MockPromise (fn) {
  let result
  let error
  const PENDING = 0
  const FULFILLED = 1
  const REJECTED = 2
  let state = PENDING
  let done = false
  const handlers = []
  this.then = function normalErrorCb (cb, errorCb) {
    return new MockPromise((resolve, reject) => {
      setTimeout(() => {
        switch (state) {
          case FULFILLED: {
            const cbValue = cb(result) //  这里的cb返回值还是promise怎么办?这里的的resolve应该是等返回的promise结束后
            return resolve(cbValue)
          }
          case REJECTED: {
            const errorValue = errorCb(error)
            reject(errorValue)
            return errorValue
          }
          case PENDING: {
            return handlers.push({ cb, errorCb })
          }
          default: {
            console.log('wrong status')
          }
        }
      }, 0)
    })
  }

  function setStatus (status, param) {
    if (done) return
    switch (status) {
      case FULFILLED: {
        done = true
        state = FULFILLED
        result = param
        handlers.forEach((handler) => handler.cb(result))
        break
      }
      case REJECTED: {
        done = true
        state = REJECTED
        error = param
        handlers.forEach((handler) => handler.errorCb(error))
        break
      }
      default: {
        console.log('pending...')
      }
    }
  }

  fn(resolve, reject)

  // function resolve (successValue) { // 默认是普通值直接传递给cb了
  //   setStatus(FULFILLED, successValue)
  // }
  function resolve (successValue) {
    const t = typeof successValue
    if (t === 'object' || t === 'function') {
      const then = successValue.then
      if (typeof then === 'function') {
        then((result) => {
          resolve(result)
        }, (error) => {
          reject(error)
        })
      }
    } else {
      setStatus(FULFILLED, successValue)
    }
  }
  function reject (error) {
    setStatus(REJECTED, error)
  }
}

const p = new MockPromise((resolve, reject) => {
  resolve('success')
  reject('error')
})
p.then((result) => {
  console.log(result)
  return new MockPromise((resolve, reject) => {
    resolve('otherSuccess')
  })
}, console.error).then(console.log, console.error)

我们基本实现了这个promise,当然还有一些可以优化的地方,比如再抽象一些函数,可以看https://www.promisejs.org/implementing/

reference:

  • https://www.promisejs.org/implementing/[Implementing promise]
  • https://github.com/cyanxxx/blogcode/tree/master/how_to_realize_promise[本文所用的代码]

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注