blog icon indicating copy to clipboard operation
blog copied to clipboard

如何实现一个简单Promise

Open BlingSu opened this issue 4 years ago • 0 comments

Promise 表示一个异步操作的最终结果,和Promise最主要的交互方式方法是通过传入它的 then 方法从而获取 Promise 最终的值或 Promise 最终拒绝的原因。

Promise 声明

首先,Promise肯定是一个类

  • promise是这么用的!new Promise((resolve, reject) => {}),所以这个参数(也就是函数)可以叫 executor,传进去就执行
  • executor里面有两个参数,一个叫resolve(成功),一个叫reject(失败)
  • 两个参数都是可以执行的,所以要先声明一下
class Promise {
  constructor (executor) {
    let resolve = () => {}
    let reject = () => {}

    executor(resolve, reject)
  }
}

Promise 状态

一个 Promise 必须只能处于一个状态中: pending, fulfilled, rejected

  • 如果是 pending 状态,则 Promise:
    • 可以转换到 fulfilledrejected 状态
    • 如果转化成 fulfilled 以后不可转为其他状态,并且必须要有一个不可以改变的值(value),通过 resolve(value)
    • 如果转化成 rejected 以后也不可以转为其他状态,并且必须要有一个不可一改变的原因(reason),通过 reject(reason)
  • 如果是 fulfilled 状态或者 fulfilled,则 Promise:
    • 不可以转换成任何其他状态
    • 必须有一个值,并且这个值不能改变
  • 如果传入执行的函数报错了,那么直接 reject()

所以以上的描述我们可以怎么做?看下面

class Promise {
  this.state = 'pending' // 设置初始时的状态
  this.value = undefined // 成功值
  this.reason = undefined // 失败原因

  let resolve = value => {
    if (this.state === 'pending') {
      this.state = 'fulfilled' // 改变成为成功状态
      this.value = value // 存储成功值
    }
  }
  let reject = reason => {
    if (this.state === 'pending') {
      this.state = 'rejected' //  改变为失败状态
      this.reason = reason // 存储失败原因
    }
  }

  try {
    executor(resolve, reject)
  } catch (error) {
    reject(error)
  }
}

then 方法

Promise 有一个叫做 then 的方法,里面有两个参数: onFulfilled, onRejected,成功的时候有成功的值,失败也有失败的原因

  • 如果状态是 fulfilled,那么执行 onFulfilled,传入 this.value。如果是 rejected,那么执行 onRejected,传入 this.reason
  • 这两个参数假设他们是函数的话,那么必须在 fulfilledrejected 后被调用,value 和 reason 依次作为他们的第一个参数
class Promise {
  // 这里代表两个参数
  then (onFulfilled, onRejected) {
    if (this.state === 'fulfilled') {
      // 如果状态变成 fulfilled,执行 onFulfilled,并且传入成功的值
      onFulfilled(this.value)
    }
    if (this.state === 'rejected') {
      onRejected(this.reason)
    }
  }
}

异步问题

上面的代码等价于一个简单的同步代码,但是如果 resolvesetTimeout 内执行 thenstate 的状态还是 pending,这样就会有问题,所以需要把无论成功与否的成功和失败的数据放到分别的数组中,然后在 reject 或者 resolve 的时候再去循环调用就可以了

class Promise {
  constructor (executor) {
    this.onResolvedCallbacks = [] // 成功值存放的数组
    this.onRejectedCallbacks = [] // 失败值存放的数组
  }

  let resolve => this.onResolvedCallbacks.forEach(fn => fn())
  let reject => this.onRejectedCallbacks.forEach(fn => fn())

  then (onFulfilled, onRejected) {
    if (this.state === 'pending') {
      this.onResolvedCallbacks.push(() => onFulfilled(this.value)) // onFulfilled传入到成功数组
      this.onRejectedCallbacks.push(() => onRejected(this.reason)) // onRejected传入到失败数组
    }
  }
}

解决链式调用

简单说就是类似 new Promise().then().then() 这样

  1. 既然要达成链式,那么就要在 then 里面返回一个新的 promise ,我们可以称成 promise2
  • 把这个 promise2 返回给下一个 promise
  • 如果返回的是一个普通的值,那么直接吧普通的值直接传给下一个 then 就好了
  1. 如果在第一个 thenreturn了一个参数(未知参数),那么 return 出来的新的 promise 就是 onFulfilled() 或者 onRejected() 的值,这个返回的值称为 x,判断 x 的函数叫做 resolvePromise
  • 判断x是不是 promise
  • 如果是的话拿到结果,作为新的 promise2 成功的结果
  • 如果是普通值,直接作为 promise2 成功的结果
  • 然后比较 promisex
  • resolvePromise 的参数有 promise2(默认返回的promise)、 x(我们自己 return 的对象)、resolvereject
  • resolverejectedpromise2
class Promise {
  then (onFulfiled, onRejected) {
    // 声明返回的 promise2
    let promise2 = new Promise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        let x = onFulfiled(this.value)
        resolvePromise(promise2, x, resolve, reject) // 这里就是处理自己return的promise和默认的promise2的关系
      }
      if (this.state === 'rejected') {
        let x = onRejected(this.reason);
        resolvePromise(promise2, x, resolve, reject)
      }
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(() => {
          let x = onFulfilled(this.value)
          resolvePromise(promise2, x, resolve, reject)
        })
        this.onRejectedCallbacks.push(() => {
          let x = onRejected(this.reason)
          resolvePromise(promise2, x, resolve, reject)
        })
      }
    })
    return promise2 // 返回promise,达成链式
  }
}

resolvePromise函数是什么

让不同的promise代码互相套用,叫做resolvePromise

  • x === promise2 的时候就会造成循环引用,相当于自己等待自己完成,就会报错
let p = new Promise(resolve => {
  resolve(0)
})
var p2 = p.then(d => {
  return p2 // 自己wait自己,永无止境
})

所以需要判断 x 不能是 null 并且是对象或者函数(包括promise)

function resolvePromise(promise2, x, resolve, reject) {
  // 解决循环引用报错问题
  if (x === promise2) {
    return reject(new TypeError('error'))
  }
  let called  // 调用标示,防止多次调用
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      let then = x.then 
      if (typeof then === 'function') { // 判断自己return的是不是function
        /** 
          这里call中,第一个参数是this,后面两个分别是成功和失败的callback
          并且成功和失败只能成功一个
        */
        then.call(x, y => { 
          if (called) return
          resolvePromise(promise2, y, resolve, reject)
        }, err => {
          if (called) return
          called = true
          reject(err)
        })
      } else {
        resolve(x) // 直接成功
      }
    } catch (err) {
      if (called) return
      called = true
      reject(err)
    }
  } else {
    resolve(x) // x是普通值
  }
}

琐碎问题

  1. onFulfilled, onRejected 都是可选参数,如果他们不是函数,必须被忽略
  • onFulfilled 返回一个普通值直接返回
  • onRejected 返回一个普通值,那么相当于 value => value,就会跑到下一个 then 中的 onFulfilled 中,所以直接抛错即可
  1. onFulfilled, onRejected 不能同步调用,必须异步,所以用 setTimeout 来解决
  • onFulfilled, onRejected 如果报错的话,直接返回 reject() 即可

完整代码如下

class Promise {
  constructor (executor) {
    this.state = 'pending'
    this.value = undefined
    this.reason = undefined
    this.onResolvedCallbacks = []
    this.onRejectedCallbacks = []
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled'
        this.value = value
        this.onResolvedCallbacks.forEach(fn => fn())
      }
    }
    let reject = reason => {
      if (this.state === 'pending') {
        this.state = 'rejected'
        this.reason = reason
        this.onRejectedCallbacks.forEach(fn => fn())
      }
    }
    try {
      executor(resolve, reject)
    } catch (err) {
      reject(err)
    }
  }
  then (onFulfilled, onRejected) {
    // 判断 onFulfilled 和 onRejected 是否函数
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }
    let promise2 = new Promise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        setTimeout(() => { // 异步
          try {
            let x = onFulfilled(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      }
      if (this.state === 'rejected') {
        setTimeout(() => { // 如果报错
          try { 
            let x = onRejected(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      }
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })
      }
    })
    // 返回promise,完成链式
    return promise2
  }
  catch (fn) {
    return this.then(null,fn)
  }
}

function resolvePromise (promise2, x, resolve, reject) {
  if (x === promise2) {
    return reject(new TypeError('err'))
  }
  let called
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      let then = x.then
      if (typeof then === 'function') { 
        then.call(x, y => {
          if (called) return
          called = true
          resolvePromise(promise2, y, resolve, reject)
        }, err => {
          if (called) return
          called = true
          reject(err)
        })
      } else {
        resolve(x)
      }
    } catch (e) {
      if (called) return
      called = true
      reject(e)
    }
  } else {
    resolve(x)
  }
}

验证

首先在 Promise后加上如下代码

Promise.defer = Promise.deferred = function () {
  let dfd = {}
  dfd.promise = new Promise((resolve,reject)=>{
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
}
module.exports = Promise
npm i promises-aplus-tests -g
promises-aplus-tests [文件名字]即可

拓展ES5方式

function testPromise (fn) {
  let self = this
  this.status = 'pending'
  this.data = undefined
  this.handleResolvedCallback = []
  this.handleRejectedCallback = []

  function resolve (value) {
    setTimeout(function () {
      if (self.status === 'pending') {
        self.status = 'resolved'
        self.data = value
        for (let i = 0; i < self.handleResolvedCallback.length; i++) {
          self.handleResolvedCallback[i](value)
        }
      }
    }, 0)
  }

  function reject (err) {
    setTimeout(function () {
      if (self.status === 'pending') {
        self.status = 'rejected'
        self.data = err
        for (let i = 0; i < self.handleRejectedCallback.length; i++) {
          self.handleRejectedCallback[i](err)
        }
      }
    }, 0)
  }

  try {
    fn(resolve, reject)
  } catch (e) {
    reject(e)
  }
}

testPromise.prototype.then = function (onResolved, onRejected) {
  let self = this
  let promise2 = null
  
  onResolved = typeof onResolved === 'function' ? onResolved : function (val) { return val }
  onRejected = typeof onRejected === 'function' ? onRejected : function (err) { return err }

  if (self.status === 'resolved') {
    return promise2 = new testPromise(function (resolve, reject) {
      setTimeout(function () {
        try {
          let b = onResolved(self.data)
          if (b instanceof testPromise) {
            b.then(resolve, reject)
          } else {
            resolve(b)
          }
        } catch (e) {
          reject(e)
        }
      }, 0)
    })
  }

  if (self.status === 'rejected') {
    return promise2 = new testPromise(function (resolve, reject) {
      setTimeout(function () {
        try {
          let b = onRejected(self.data)
          if (b instanceof testPromise) {
            b.then(resolve, reject)
          }
        } catch (e) {
          reject(e)
        }
      }, 0)
    })
  }

  if (self.status === 'pending') {
    return promise2 = new testPromise(function (resolve, reject) {
      self.handleResolvedCallback.push(function (value) {
        try {
          let b = onResolved(self.data)
          if (b instanceof testPromise) {
            b.then(resolve, reject)
          }
        } catch (e) {
          reject(e)
        }
      })
      self.handleResolvedCallback.push(function (err) {
        try {
          let b = onRejected(self.data)
          if (b instanceof testPromise) {
            b.then(resolve, reject)
          }
        } catch (e) {
          reject(e)
        }
      })
    })
  }
}

testPromise.prototype.catch = function (onRejected) {
  return this.then(null, onRejected)
}

拓展ES6 方式

class testPromise {
  constructor(fn) {
    this.status = 'pending'
    this.value = null
    this.fnArr = { resolved: [], rejected: [] }

    let handle = (status, value) => {
      if (this.status === 'pending') {
        this.status = status
        this.value = value
        this.fnArr[status].forEach(fn => {
          fn.call(this, status)
        })
      }
    }

    let resolve = handle.bind(this, 'resolved')
    let reject = handle.bind(this, 'rejected')

    fn(resolve, reject)
  }

  then(resFn, rejFn) {
    if (!isFn(resFn)) resFn = (val) => val
    if (!isFn(rejFn)) rejFn = (err) => err

    return new testPromise((resolve, reject) => {
      let resolveFn = (val) => {
        setTimeout(() => {
          try {
            let v = resFn(val)
            if (isThen(v)) {
              v.then(resolve, reject)
            } else {
              resolve(v)
            }
          } catch(e) {
            reject(e)
          }
        }, 0)
      }

      let rejectFn = (err) => {
        try {
          let e = rejFn (err)
          if (isThen(e)) {
            e.then(resolve, reject)
          }
        } catch(e) {
          reject(e)
        }
      }

      switch (this.status) {
        case 'pending':
          this.fnArr['resolved'].push(resolveFn)
          this.fnArr['rejected'].push(rejectFn)
          break
        case 'resolved':
          resolveFn(this.value)
          break
        case 'rejected':
          rejectFn(this.value)
          break
      }
    })
  }
}


const isFn = (fn) => {
  return typeof fn === 'function'
}

const isThen = (val) => {
  return val && this.isFn(val.then)
}

BlingSu avatar Oct 29 '20 01:10 BlingSu