关于javascript:手写符合-PromisesA-规范的-Promise

前言

Promise 在开发中咱们常常用到,它解决了回调天堂问题,对谬误的解决也十分不便。本文我将通过一步步欠缺 Promise,从简略到简单的过程来说。

本文适宜纯熟使用 Promise 的人浏览。

极简版

首先Promise是一个类,类中的构造函数须要接管一个执行函数executor默认就会执行,它有两个参数:resolvereject,这两个参数是Promise外部定义的两个函数,用来扭转状态并执行对应回调函数。

默认创立一个Promise状态就是pendingpromise只有三种状态:pending,fulfilled,rejected,调用胜利resolve和失败reject办法时,须要传递一个胜利的起因/值value和失败的起因reason。每一个promise实例都有一个 then 办法。

Promise状态一经扭转就不能再扭转,故咱们限度只能在状态为pending才扭转,这样就保障状态只能扭转一次。

如果抛出异样依照失败来解决。

依照以上Promise的根本要求,就有个根本构造:

const STATUS = {
  PENDING: 'PENDING',
  FULFILLED: 'FULFILLED',
  REJECTED: 'REJECTED',
}

class Promise {
  constructor(executor) {
    this.status = STATUS.PENDING
    this.value = undefined // 胜利的值
    this.reason = undefined // 失败起因
    this.onResolvedCallbacks = [] // 寄存胜利的回调
    this.onRejectedCallbacks = [] // 寄存失败的回调

    const resolve = val => {
      if (this.status === STATUS.PENDING) {
        this.status = STATUS.FULFILLED
        this.value = val
        this.onResolvedCallbacks.forEach(fn => fn())
      }
    }

    const reject = reason => {
      if (this.status === STATUS.PENDING) {
        this.status = STATUS.REJECTED
        this.reason = reason
        this.onRejectedCallbacks.forEach(fn => fn())
      }
    }

    try {
      executor(resolve, reject)
    } catch (e) {
      console.log(e)
    }
  }

  then(onFulfilled, onRejected) {
    if (this.status === STATUS.FULFILLED) {
      onFulfilled(this.value)
    }
    if (this.status === STATUS.REJECTED) {
      onRejected(this.reason)
    }
    if (this.status === STATUS.PENDING) {
      this.onResolvedCallbacks.push(() => {
        onFulfilled(this.value)
      })
      this.onRejectedCallbacks.push(() => {
        onRejected(this.reason)
      })
    }
  }
}

下面代码中,为什么不间接执行回调还要存储呢?因为如果当resolve(3)被提早执行时,此时常理写代码来说 then 是会被执行的,但此时没有resolve,故p的状态应为pending,不应立即执行胜利调用的函数,须要把它存起来,直到执行resolve再执行胜利调用的函数。

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(3)
  }, 1000)
})
p.then(res => {
  console.log('then1', res)
  return 2
})

链式调用

链式调用想必大家多少有点理解,在 jQuery 外面的链式调用则是返回 this,而 Promise 外面的链接调用则是返回一个新的 Promise 对象。

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(3)
  }, 1000)
})
p.then(res => {
  console.log('then1', res) // then1 3
  return 2
}).then(res => {
  // 链式调用
  console.log('then2', res) // then2 2
})

批改then办法和减少catch办法

then(onFulfilled, onRejected) {
  let nextPromise = new Promise((resolve, reject) => {
    if (this.status === STATUS.FULFILLED) {
      setTimeout(() => {
        try {
          let res = onFulfilled(this.value)
          resolve(res)
        } catch (e) {
          reject(e)
        }
      })
    }
    if (this.status === STATUS.REJECTED) {
      setTimeout(() => {
        try {
          let res = onRejected(this.reason)
          resolve(res)
        } catch (e) {
          reject(e)
        }
      })
    }
    if (this.status === STATUS.PENDING) {
      this.onResolvedCallbacks.push(() => {
        setTimeout(() => {
          try {
            let res = onFulfilled(this.value)
            resolve(res)
          } catch (e) {
            reject(e)
          }
        })
      })
      this.onRejectedCallbacks.push(() => {
        setTimeout(() => {
          try {
            let res = onRejected(this.reason)
            resolve(res)
          } catch (e) {
            reject(e)
          }
        })
      })
    }
  })
  return nextPromise
}

catch(err) {
  // 默认没有胜利,只有失败
  return this.then(undefined, err)
}

此次批改咱们是在最外层包了新的Promise,而后加了个 setTimeout 模仿微工作(因为这里用的 setTimeout 模仿微工作,所以 JS 事件循环执行程序上和原生 Promise 有区别),把回调放入,期待确保异步执行。

链式调用进阶版

下面的Promise仍旧没有过关,因为如果链式调用中Promise返回的是一般值,就应该把值包装成新的Promise对象

  • 每个 then 办法都返回一个新的 Promise 对象(重点)
  • 如果 then 办法返回了一个 Promise 对象,则须要查看它的状态,如果状态是胜利,则调用resolve办法,把胜利的状态传递给它;如果是失败的,则把失败的状态传递给下一个Promise对象。
  • 如果 then 办法中返回的是一个原始数据类型值(如 Number、String 等)就应用此值包装成一个新的 Promise 对象返回。
  • 如果 then 办法中没有 return 语句,则返回一个用 undefined 包装的 Promise 对象
  • 如果 then 办法没有传入任何回调,则持续向下传递(值的传递个性)。
  • 如果是循环援用则须要抛出谬误

批改then办法

then(onFulfilled, onRejected) {
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x
  onRejected =
    typeof onRejected === 'function'
      ? onRejected
      : err => {
          throw err
        }
  let nextPromise = new Promise((resolve, reject) => {
    if (this.status === STATUS.FULFILLED) {
      setTimeout(() => {
        try {
          let res = onFulfilled(this.value)
          resolvePromise(res, nextPromise, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })
    }

    if (this.status === STATUS.REJECTED) {
      setTimeout(() => {
        try {
          let res = onRejected(this.reason)
          resolvePromise(res, nextPromise, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })
    }
    if (this.status === STATUS.PENDING) {
      this.onResolvedCallbacks.push(() => {
        setTimeout(() => {
          try {
            let res = onFulfilled(this.value)
            resolvePromise(res, nextPromise, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      })
      this.onRejectedCallbacks.push(() => {
        setTimeout(() => {
          try {
            let res = onRejected(this.reason)
            resolve(res)
          } catch (e) {
            reject(e)
          }
        })
      })
    }
  })
  return nextPromise
}

外面减少了一个resolvePromise函数,解决调用then办法后不同返回值的状况 ,实现如下:

function resolvePromise(x, nextPromise, resolve, reject) {
  if (x === nextPromise) {
    // x 和 nextPromise 指向同一对象,循环援用抛出谬误
    return reject(new TypeError('循环援用'))
  } else if (x && (typeof x === 'object' || typeof x === 'function')) {
    // x 是对象或者函数

    let called = false // 防止屡次调用
    try {
      let then = x.then // 判断对象是否有 then 办法
      if (typeof then === 'function') {
        // then 是函数,就判定 x 是一个 Promise(依据Promise A+标准)
        then.call(
          x,
          function (y) {
            // 调用返回的promise,用它的后果作为下一次then的后果
            if (called) return
            called = true
            resolvePromise(y, nextPromise, resolve, reject) // 递归解析胜利后的值,直到它是一个一般值为止
          },
          function (r) {
            if (called) return
            called = true
            reject(r) // 取then时产生谬误了
          }
        )
      } else {
        resolve(x) // 此时 x 就是一个一般对象
      }
    } catch (e) {
      reject(e)
    }
  } else {
    // x 是原始数据类型 / 没有返回值,这里即是undefined
    resolve(x)
  }
}

Promise.resolve() 实现

class Promise {
  // ...
  static resolve(val) {
    return new Promise((resolve, reject) => {
      resolve(val)
    })
  }
}

Promise.reject() 实现

class Promise {
  // ... 
  static reject(reason) {
    return new Promise((resolve, reject) => {
      reject(reason)
    })
  }
}

Promise.prototype.finally 实现

该办法次要有两大重点

  • 无论以后这个Promise对象最终的状态是胜利还是失败 ,finally办法里的回调函数都会执行一次
  • 在finally办法前面能够持续链式调用then 办法,拿到以后这个Promise对象最终返回的后果
Promise.prototype.finally = function (callback) {
  return this.then(
    data => {
      // 让函数执行,外部会调用办法,如果办法是promise须要期待它实现
      return Promise.resolve(callback()).then(() => data)
    },
    err => {
      return Promise.resolve(callback()).then(() => {
        throw err
      })
    }
  )
}

Promise.all() 实现

Promise.all能够将多个Promise实例包装成一个新的Promise实例。同时,胜利和失败的返回值是不同的,胜利的时候返回的是一个后果数组,而失败的时候则返回最先被reject失败状态的值。

Promise.all = function (promises) {
  if (!Array.isArray(promises)) {
    throw new Error('Not a array')
  }
  return new Promise((resolve, reject) => {
    let result = []
    let times = 0 // 计数器
    function processData(index, val) {
      result[index] = val
      if (++times === promises.length) {
        resolve(result)
      }
    }
    for (let i = 0; i < promises.length; i++) {
      let p = promises[i]
      if (isPromise(p)) {
        // Promise对象
        p.then(data => {
          processData(i, data)
        }, reject)
      } else {
        processData(i, p) // 一般值
      }
    }
  })
}

Promise.race() 实现

在执行多个异步操作中,只保留取第一个执行实现的异步操作的后果,即是哪个后果取得的快,就返回那个后果,不论后果自身是胜利状态还是失败状态。其余的办法仍在执行,不过执行后果会被摈弃。

Promise.race = function (promises) {
  if (!Array.isArray(promises)) {
    throw new Error('Not a array')
  }
  return new Promise((resolve, reject) => {
    if (promises.length === 0) {
      return
    } else {
      for (let p of promises) {
        resolve(p).then(
          value => {
            resolve(value)
          },
          reason => {
            reject(reason)
          }
        )
      }
    }
  })
}

到此为止,Promise 就实现实现了。

残缺源码地址

  • ps: 集体技术博文 Github 仓库,感觉不错的话欢送 star,激励我持续写作吧~

评论

发表回复

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

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理