Promise原理以及实现(Promise/A+规范) 下

实现基础的雏形(成功状态resolve):

function oPromise(fn) {
  var value = null,
    callbacks = []  //callbacks为数组, 由于可能同时有不少个回调
  this.then = function (fulfilled) {
    callbacks.push(fulfilled)
    return this //
  }
  function resolve(newValue) {
    value = newValue
    callbacks.forEach(function (callback) {
      callback(value)
    })
  }
  fn(resolve)
}

new oPromise((resolve) => { //省略了rejected, 方便之后阅读, 由于与resolve原理同样
  setTimeout(() => { //处理异步
    resolve('dz')
  }, 0)
}).then(res => {
  console.log(res)
})
复制代码

大体逻辑:javascript

  1. new oPromise实例, 执行fn(), 传入的是内部resolve函数, 由于传入的回调有setTimeout定时器是一个异步, 因此resolve会被延迟执行
  2. 而后就是执行then方法, 把then包含的回调注册(添加)到callbacks数组中, 由于返回了实例->this, 所以若是有序还有其余then会一次被注册到callbacks
  3. 而后执行被延时的setTimeout, 并执行了resolve, 循环执行数组, 而后把value赋值给回调

问题:java

  • Promise/A+规范要求回调须要经过异步方式执行, 虽然已经设置了setTimeout, 可是若是没有设置的话, oPromise内部的回调执行任然是同步, 这样会致使then方法尚未执行就执行了resolve, 此时callbacks里仍是空的..

意思就是说, 实例化的时候传入了执行函数, 这个函数内部并非全部时候都是异步的, 也有时候是同步代码例如:数组

new oPromise(resolve => {
  let a = 1 + 1
  resolve(a)
}).then(res => console.log) // 2
复制代码

这个时候全部代码都是同步的所以会先执行回调, 而后再执行 then, 这样的话 callbacks 数组先被执行, 而后最后添加回调, 这样的意义何在呢...promise

所以, 将 resolve 延迟执行(也知足了 Promise/A+ 规范):bash

// resolve 函数
function resolve(newValue) {
  value = newValue
  setTimeout(() => {
    callbacks.forEach(function (callback) {
      callback(value)
    })
  }, 0)
}
复制代码

加上状态

Promises/A+规范规定, pending能够转化为fulfilled或rejected而且只能转化一次, 也就是说若是pending转化到fulfilled状态, 那么就不能再转化到rejected. 而且fulfilled和rejected状态只能由pending转化而来, 二者之间不能互相转换
复制代码

加上状态后:异步

function oPromise(fn) {
  let value = null,
      state = 'pending'
      callbacks = []  //callbacks为数组, 由于可能同时有不少个回调

  this.then = function (fulfilled) {
    if (state === 'pending') {
      callbacks.push(fulfilled)
    } else {
      fulfilled(value)
    }
    return this //返回实例
  }

  function resolve(newValue) {
    if (state !== 'pending') return
    setTimeout(() => {
      value = newValue
      state = 'fulfilled'
      callbacks.forEach(function (callback) {
        callback(value)
      })
    }, 0)
  }

  fn(resolve)
}

new oPromise((resolve) => { //省略了rejected, 方便之后阅读, 由于与resolve原理同样, 后续再加入
  setTimeout(() => { //处理异步
    resolve('dz')
  }, 0)
}).then(res => {
    console.log(res)
    return 'dz'
   })
复制代码

resolve执行时将状态变为fulfilled, 若是回调是异步, 就会先执行 then, 此时的 state 为pending, 就将 then 注册的回调添加到callbacks中, 若是回调不是异步, 就会由于state为 fulfilled 而当即执行函数

resolve 方法中第一条语句是判断状态是否为 pending, 这条语句的缘由是: 一旦状态被 resolve 或者 reject 改变后 就不能再改变了, 在后续 写到 加入 reject 后 再详细说明post

链式调用

每一次调用 fulfilled 回调 咱们都把返回值保存下来, 以让下一个then中的回调(也就是下一个 fulfilled)得到返回值, 简单改造一下ui

function oPromise(fn) {
  let value = null,
      state = 'pending'
      callbacks = [] //callbacks为数组, 由于可能同时有不少个回调

  this.then = function (fulfilled) {
    if (state === 'pending') {
      callbacks.push(fulfilled)
    } else {
      fulfilled(value)
    }
    return this //返回实例
  }

  function resolve(newValue) {
    if (state !== 'pending') return
    setTimeout(() => {
      value = newValue // 这里放在setTimeout才比较正确 否则不能链式调用
      state = 'fulfilled'
      callbacks.forEach(function (callback) {
        value = callback(value) // 保存上一个value: 关键
      })
    }, 0)
  }
  fn(resolve)
}

new oPromise((resolve) => { //省略了rejected, 方便之后阅读, 由于与resolve原理同样
  setTimeout(() => { //处理异步
    resolve('dz')
  }, 0)
}).then(res => {
    console.log(res)
    return 'dz'
  }).then(res => {
    console.log(res)
  })
复制代码

虽然上面看似没有问题, 但其实有很大的缺陷:this

若是then的执行回调中的代码为异步, 此时不能保证异步执行后 返回的值能被下一个then执行回调所接收. 不对, 是确定不能

所以, 解决异步并回调的根本方式就是回到最初的问题 --> Promise

若是then回调函数中的代码有异步代码, 就将异步代码放置在 Promise中, 而后将返回值传给下一个 then中的回调, 这样便造成 层层链式, 多安逸...

链式Promise是指在当前promise达到fulfilled状态后, 即开始进行下一个promise(后邻promise)

所以就须要在then方法里return一个Promise

function oPromise(fn) {
  let value = null,
      state = 'pending',
      callbacks = []  //callbacks为数组, 由于可能同时有不少个回调

  this.then = function (fulfilled) {
    return new oPromise((resolve) => {
      handle({
        fulfilled: fulfilled || null,
        resolve // 将下一个promise的resolve和then的回调一块儿添加到callbacks中, 为了在本次执行resolve时, 将本次的返回值传递到下一个promise
      })
    })
  }

  function handle(callback) {
    if (state === 'pending') {
      callback.push(callback)
      return
    }

    if (!callback.fulfilled) { // then 没有回调函数
      callback.resolve(value) // 就直接将value值传给-> 下 -> 下 个promise(若是有的话)
      return
    }
    const ret = callback.fulfilled(value) // 接受上一个promise返回值
    callback.resolve(ret) // 传入下一个promise... 有点晕, 要仔细想一想..
  }
  function resolve(newValue) { //若是then函数的回调为一个新的promise, 须要作一些特殊处理
    if (state !== 'pending') return
    if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
      const then = newValue.then // 获取 promise的then
      if (typeof then === 'function') {
        then.call(newValue, resolve) // 添加then方法的回调, 把本次then回调的执行结果传给下一个promise, 并执行
        return
      }
    }

    const fn = () => {
      value = newValue
      state = 'fulfilled'
      handleCb()
    }
    setTimeout(fn, 0)
  }

  function handleCb() {
    while (callbacks.length) {
      const cb = callbacks.shift()
      handle(cb)
    }
  }
  fn(resolve)
}
复制代码

整个过程的关键在于handle函数的执行, 它分担了联系相邻两个promise的做用:

  1. 根据state来处理是添加回调仍是执行, 若是没有回调就跳过本次promise执行再下一个then(promise)
  2. 若是是pending就是添加回调, 把下一个then产生promise中的resolve一块儿添加
  3. 若是是fulfilled就是执行, 就会调用下一个promise的resolve, 并传入本次回调产生的结果值

失败处理 reject

在异步操做失败时, 标记其状态为rejected, 并执行注册的失败回调
复制代码
function oPromise(fn) {
  let value = null,
    state = 'pending',
    callbacks = []  //callbacks为数组, 由于可能同时有不少个回调
  this.then = function (fulfilled, rejected) {
    return new oPromise((resolve, reject) => {
      handle({
        fulfilled: fulfilled || null,
        rejected: rejected || null,
        resolve,
        reject // 和resolve同样, 将一下个promise的错误执行(reject)加入callbacks中
      })
    })
  }
  function handle(callback) {
    if (state === 'pending') {
      callbacks.push(callback)
      return
    }
    let cb = state === 'fulfilled' ? callback.fulfilled : callback.rejected
    let r = state === 'fulfilled' ? callback.resolve : callback.reject
    let ret = null
    if (cb === null) {
      cb(value)
      return
    }
    ret = cb(value)
    r(ret)
  }

  // 省略 resolve, 减小篇幅. function resolve(newValue) {...}

  function reject(reason) {
    if (state !== 'pending') return
    const fn = () => {
      if (state !== 'pending') return
      if (reason && (typeof reason === 'object' || typeof reason === 'function')) {
        const then = reason.then // 获取 promise的then
        if (typeof then === 'function') {
          then.call(reason, resolve) // 添加then方法的回调, 把本次then回调的执行结果传给下一个promise, 并执行
          return
        }
      }
      state = 'rejected'
      value = reason
      handleCb()
    }
    setTimeout(fn, 0)
  }
  function handleCb() { // 将公共部分处理提取出来
    while (callbacks.length) {
      const cb = callbacks.shift()
      handle(cb)
    }
  }

  fn(resolve, reject)
}
复制代码

加入了失败状态的处理以后, 大体原理仍是同样, 只是在执行callbacks里面的回调和执行下一个promise的处理(resolve或者reject)须要作一些判断

这里说一下 resolvereject 方法中第一个判断语句的做用, 举个栗子, 假如没有判断:

const promise = new oPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)
  }, 100)
  setTimeout(() => {
    reject(2)
  }, 100)
})
复制代码

上面代码执行后 200ms 后 promise 的状态为 rejected 而不是 fulfilled, 原则上 一旦改变就不能再改变了

异常处理 catch

若是在执行过程当中发生代码错误, 那么就用try catch来捕获错误, 并把错误转成失败状态, 就是把promise的状态设为rejected状态, 执行后续错误的回调

改造handle方法

function handle(callback) {
    if (this.state === 'pending') {
      callbacks.push(callback)
      return
    }
    const { fulfilled, rejected, resolve, reject } = callback
    const cb = this.state === 'fulfilled' ? fulfilled : rejected
    const next = this.state === 'fulfilled' ? resolve : reject
    if (!cb) { // 没有 回调 就直接进入下一个promise
      next(value)
      return
    }

    try {
      const ret = cb(value)
      // ①
      resolve(ret) // 这里始终是 resolve, 即时 state 是 rejected
    } catch (error) {
      callback.reject(error)
    }allback.reject(e) //错误 直接执行失败状态回调
  }
}
复制代码

须要注意的就是, 上面 代码 注释 处 始终是 resolve 的缘由是 和 写的这篇Promise 上 所说的 这一级 执行错误回调, 可是返回值仍然是下一级的 resolved 回调 接收并执行

异常处理能够直接使用 catch 来接受

this.catch = rejected => {
  this.then(null, rejected) // 在 链式调用末尾再添加一个只有 rejected 的 then
}
复制代码

这样就实现了catch 功能, 若是前面then中都没有 rejected 回调(由于值都为 null) , 会直接将值传给最后catch(也就是最后的then)

静态方法 oPromise.resolve

Promise.resolve 的入参可能有如下几种状况:

  • 无参数 [直接返回一个resolved状态的 Promise 对象]
  • 普通数据对象 [直接返回一个resolved状态的 Promise 对象]
  • promise对象 [返回一个新的Promise对象而且这个对象的 resolve 方法 交给传入的promise对象来执行]
// function oPromise(fn).then(...)
oPromise.resolve = function (value) {
  if (state !== 'pending') return
  if (value && (typeof value === 'object' && typeof value.then === 'function')) {
    const { then } = value
    return new oPromise(resolve => {
      then(resolve) // ②
    })
  } else if (value) {
    return new oPromise(resolve => resolve(value))
  } else {
    return new oPromise(resolve => resolve())
  }
}

oPromise.resolve(1).then(res => console.log(res)) // 1
复制代码

上面代码中 处的意思就是, 当传入的参数(value) 是这样的:

let value = new Promise(resolve => {
  resolve(1)
}).then(res => {
  return res + 1
})
Promise.resolve(value)
       .then(res => console.log(res)) // 2
复制代码

虽然 value 已是一个 promise 实例了, 貌似直接返回(如上面代码中的), 而后再被后续的 then 注册是没有问题的. 可是 value 是一个失败状态的 promise 实例呢:

let value = new Promise((resolve, reject) => {
  reject(1)
}).then(res => {
  return res + 1
}, err => {
  console.log(err)
  return err + 1
})
Promise.resolve(value).then(res => {
  console.log(res)
}, err => {
  console.log(err) // what?? 我是2???
})
复制代码

若是像 处那样直接返回, 后续的 then 实例中的回调是会被执行 rejected 回调的, 这样就是说, 我 oPromise.resolve 竟然返回的是一个失败状态的 promise ??? Oh, No

静态方法 oPromise.reject

Promise.rejectPromise.resolve 相似, 区别在于 Promise.reject 始终返回一个状态的rejected的Promise实例, 而 Promise.resolve 的参数若是是一个 Promise 实例的话, 返回的是参数对应的 Promise 实例, 因此状态不必定

oPromise.reject = function (value) {
  return new Promise(function(resolve, reject) {
    reject(value)
  })
}
复制代码

静态方法 oPromise.all

入参是一个 Promise 的实例数组, 而后注册一个 then 方法, 而后是数组中的 Promise 实例的状态都转为 fulfilled 以后则执行 then 方法. 这里主要就是一个计数逻辑, 每当一个 Promise 的状态变为 fulfilled 以后就保存该实例返回的数据, 而后将计数减一, 当计数器变为 0 时, 表明数组中全部 Promise 实例都执行完毕.

oPromise.all = function (arr) {
  let args = Array.prototype.slice.call(arr)
  return new Promise(function (resolve, reject) {
    if (args.length === 0) return resolve([])
    let remaining = args.length
    function res(i, val) {
      try {
        if (val && (typeof val === 'object' || typeof val === 'function')) {
          let then = val.then
          if (typeof then === 'function') {
            then.call(val, function (val) { // 这里若是传入参数是 promise的话须要将结果传入 args, 而不是 promise实例
              res(i, val) 
            }, reject)
            return
          }
        }
        args[i] = val
        if (--remaining === 0) {
          resolve(args)
        }
      } catch (ex) {
        reject(ex)
      }
    }
    for (let i = 0; i < args.length; i++) {
      res(i, args[i])
    }
  })
}
复制代码

静态方法 oPromise.race

有了 oPromise.all 的理解, oPromise.race 理解起来就更容易了. 它的入参也是一个 Promise 实例数组, 而后其 then 注册的回调方法是数组中的某一个 Promise 的状态变为 fulfilled 的时候就执行. 由于 Promise 的状态只能改变一次, 那么咱们只须要把 Promise.race 中产生的 Promise 对象的 resolve 方法, 注入到数组中的每个 Promise 实例中的回调函数中便可.

oPromise.race = function (args) {
  return new oPromise((resolve, reject) => {
    for (let i = 0, len = args.length; i < len; i++) {
      args[i].then(resolve, reject)
    }
  })
}
复制代码

finally

无论Promise最后的状态如何 都要执行一些最后的操做. 咱们把这些操做放到 finally 中 也就是说 finally 注册的函数是与 Promise 的状态无关的 不依赖 Promise 的执行结果

function oPromise(fn) {
  // ...
this.finally = function(done) {
  this.then(() => {
    done()
  }, () => {
    done()
  })
}
  // ...
}
复制代码

之因此没有吧 done 直接传给 then 是由于 原版 Promisefinally 执行回调中并无传入任何参数

偷偷地作了修改...

较完整代码

function oPromise(fn) {
  this.state = 'pending'
  this.value = null
  let callbacks = []
  this.then = function (fulfilled, rejected) {
    return new oPromise((resolve, reject) => {
      handle({
        fulfilled: fulfilled,
        rejected: rejected,
        resolve,
        reject
      })
    })
  }
  this.catch = function (rejected) {
    this.then(null, rejected)
  }
  this.finally = function (done) {
    this.then(() => {
      done()
    })
  }
  const handle = (callback) => {
    if (this.state === 'pending') {
      callbacks.push(callback)
      return
    }
    const { fulfilled, rejected, resolve, reject } = callback
    const cb = this.state === 'fulfilled' ? fulfilled : rejected
    const next = this.state === 'fulfilled' ? resolve : reject
    if (!cb) {
      next(value)
      return
    }
    try {
      const ret = cb(value)
      resolve(ret)
    } catch (error) {
      callback.reject(error)
    }
  }
  const resolve = newValue => {
    if (state !== 'pending') return
    const fn = () => {
      if (this.state !== 'pending') return
      if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
        const then = newValue.then
        if (typeof then === 'function') {
          then.call(newValue, resolve)
          return
        }
      }
      this.state = 'fulfilled'
      value = newValue
      handleCb()
    }
    setTimeout(fn, 0)
  }
  const reject = newValue => {
    if (state !== 'pending') return
    const fn = () => {
      if (this.state !== 'pending') return
      if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
        const { then } = newValue
        if (typeof then === 'function') {
          then.call(newValue, reject)
          return
        }
      }
      value = newValue
      this.state = 'rejected'
      handleCb()
    }
    setTimeout(fn, 0)
  }
  const handleCb = _ => {
    while (callbacks.length) {
      const cb = callbacks.shift()
      handle(cb)
    }
  }
  fn(resolve, reject)
}
oPromise.resolve = function (value) {
  if (value && value instanceof oPromise) {
    return value
  } else if (value && (typeof value === 'object' && typeof value.then === 'function')) {
    const { then } = value
    return new oPromise(resolve => {
      then(resolve)
    })
  } else if (value) {
    return new oPromise(resolve => resolve(value))
  } else {
    return new oPromise(resolve => resolve())
  }
}
oPromise.race = function (args) {
  return new oPromise((resolve, reject) => {
    for (let i = 0, len = args.length; i < len; i++) {
      args[i].then(resolve, reject)
    }
  })
}
oPromise.all = function (arr) {
  let args = Array.prototype.slice.call(arr)
  return new Promise(function (resolve, reject) {
    if (args.length === 0) return resolve([])
    let remaining = args.length
    function res(i, val) {
      try {
        if (val && (typeof val === 'object' || typeof val === 'function')) {
          let then = val.then
          if (typeof then === 'function') {
            then.call(val, function (val) {
              res(i, val)
            }, reject)
            return
          }
        }
        args[i] = val
        if (--remaining === 0) {
          resolve(args)
        }
      } catch (ex) {
        reject(ex)
      }
    }
    for (let i = 0; i < args.length; i++) {
      res(i, args[i])
    }
  })
}
复制代码

由于函数(function)中 this 指向问题, 我把全部的函数都换成了 箭头函数, 由于能够直接使用 this 嘿嘿嘿....

嘿嘿

原文出自 这里, 大部分都是 copy 的, 嘿嘿. 可是加入了本身的理解, 嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿...

相关文章
相关标签/搜索