模拟 ECMAScript 6 Promise

157 阅读1分钟
原文链接: github.com

Promise 现在是 JS 的常用特性了. 我在阅读你不知道的 JavaScript 这本书时尝试根据书中提供的特性实现了一个 Promise 的模拟. 写的过程中没有参照 Q, bluebird 等开源库, 所以和他们的实现, 以及语言原生的实现可能完全不同, 仅供抛砖引玉之用. 这篇文章恰当的标题或许是: 如果让你来实现 Promise 你会怎么做.

你可以去我的 playground wzlib 找到源代码和单元测试.

function Prom(immediate, res_handler, err_handler) {
  p = this
  p.subs = []
  p.status = 0 // 0 pending, 1 resolved, 2 rejected
  p.res_handler = res_handler
  p.err_handler = err_handler
  p.val = null
  p.err = null

  // created two closures, they would be passed to cb as resolve and reject
  p.res = function(val) {
    if (p.status === 0) {
      p.status = 1
      p.val = val
      setTimeout(() => {
        const next_val = res_handler && res_handler.call(null, val)
        p.subs.forEach(sub_p => {
          sub_p.notify(1, next_val || val)
        })
      })
    }
  }
  p.rej = function(err) {
    if (p.status === 0) {
      p.status = 2
      p.err = err
      setTimeout(() => {
        err_handler && err_handler.call(null, err)
        p.subs.forEach(sub_p => {
          sub_p.notify(2, err)
        })
      })
    }
  }

  if (typeof immediate === 'function') immediate.call(null, p.res, p.rej)
}

Prom.prototype.then = function(res_handler, err_handler) {
  p = this
  var sub_p = new Prom(null, res_handler, err_handler)
  p.subs.push(sub_p)
  if (p.status !== 0) {
    p.status === 1 ? sub_p.notify(1, p.val) : sub_p.notify(2, p.err)
  }
  return sub_p
}

Prom.prototype.catch = function(err_handler) {
  p = this
  var sub_p = new Prom(null, null, err_handler)
  p.subs.push(sub_p)
  if (p.status === 2) sub_p.notify(2, p.err)
  return sub_p
}

Prom.prototype.notify = function(status, playload) {
  p = this
  status === 1 ? p.res(playload) : p.rej(playload)
}

Prom.resolve = function(val) {
  if (val instanceof Prom) return val

  const p = new Prom()
  p.notify(1, val)
  return p
}

Prom.reject = function(err) {
  if (val instanceof Prom) return val

  const p = new Prom()
  p.notify(2, err)
  return p
}

Prom.all = function all(jobs) {
  if (!Array.isArray(jobs) || jobs.length === 0) return Prom.reject()

  const len = jobs.length
  const values = new Array(len)
  let counter = 0

  return new Prom((resolve, reject) => {
    for (let i = 0; i < len; i++) {
      Prom.resolve(jobs[i])
        .then(value => {
          values[i] = value
          if (++counter === len) resolve(values)
        })
        .catch(err => reject(err))
    }
  })
}

Prom.race = function race(jobs) {
  if (!Array.isArray(jobs) || jobs.length === 0) return Prom.reject()

  return new Prom((resolve, reject) => {
    jobs.forEach(job => {
      Prom.resolve(job)
        .then(value => resolve(value))
        .catch(err => reject(err))
    })
  })
}

Prom.none = function(jobs) {
  if (!Array.isArray(jobs) || jobs.length === 0) return Prom.resolve()

  const len = jobs.length
  let counter = 0

  return new Prom((resolve, reject) => {
    for (let i = 0; i < len; i++) {
      Prom.resolve(jobs[i])
        .then(value => reject({ i, value }))
        .catch(err => {
          if (++counter === len) resolve()
        })
    }
  })
}

Prom.any = function any(jobs) {
  if (!Array.isArray(jobs) || jobs.length === 0) return Prom.resolve()

  return new Prom((res, rej) => {
    jobs.forEach(job => {
      Prom.resolve(job)
        .then(value => res(value))
        .catch(err => {
          if (++counter === len) reject()
        })
    })
  })
}

Prom.last = function last(jobs) {
  if (!Array.isArray(jobs) || jobs.length === 0) return Prom.resolve()

  const len = jobs.length
  let counter = 0

  return new Prom((res, rej) => {
    jobs.forEach(job => {
      Prom.resolve(job)
        .then(value => {
          if (++counter === len) res(value)
        })
        .catch(err => rej(err))
    })
  })
}

module.exports = Prom