自己鼓捣一个Promise

2019-04-18 admin

image

源码

very-simple-promise,包含了不完善的单元测试❤️.

感谢

代码君的自白

这篇的文章主要参考了上面的博客,谢谢他的帮助🙏。

Promise/A+规范

Promises/A+规范, 下面👇是对规范的内容的部分翻译。英文比较烂,不喜欢的可以不看

承诺必须满足三种状态, pending(等处理), fulfilled(履行), rejected(拒绝)

Promises的状态

  • promise处于pending时

    • 可以过渡到fulfilled或者rejected状态
  • promise处于fulfilled时

  • promise处于rejected时

then

promise的then方法接受两个参数

promise.then(onFulfilled, onRejected)
  • onFulfilled和onRejected都是可选参数

    • onFulfilled不是函数需要忽略
    • onRejected不是函数需要忽略
  • onFulfilled如果是一个函数

    • onFulfilled在promise状态为fulfilled时被调用
    • onFulfilled只能被调用一次
    • onFulfilled不能在fulfilled之前不能调用
  • onRejected如果是一个函数

    • onRejected在promise状态为rejected时被调用
    • 在rejected之前不能调用它
    • onRejected只能被调用一次
  • onFulfilled or onRejected must not be called until the execution context stack contains only platform code. (原文不太懂什么意思,但是规范给出了注解)

  • onFulfilled和onRejected必须作为函数调用

  • then可以在同一个promise多次调用

    • 如果promise被履行, 则onFulfilled回调必须按照then的顺序执行
    • 如果promise被拒绝, 则onRejected回调必须按照then的顺序执行
promise2 = promise1.then(onFulfilled, onRejected);
  • then必须返回promise

    • 如果onFulfilled或者onRejected返回一个值,则运行处理Promise的过程[[Resolve]](promise2, x)(这里处理的Resolve(promise2, x), onFulfilled或者onRejected的返回值会被resolve处理, resolve的第一个参数是then返回的新的promise)
    • 如果onFulfilled, onRejected抛出错误error, 则promise2必须为rejected
    • 如果onFulfilled不是一个函数, 而promise1的状态为fulfilled, promise2必须与promise1具有相同的值被履行
    • 如果onRejected不是一个函数, 而promise1的状态为rejected, promise2必须与promise1具有相同的值被拒绝

处理Promise

[[Resolve]](promise, x)需要遵循以下规范

  • 如果promise与x相等, 抛出TypeError的错误, 并以错误为理由拒绝promise

  • 如果x是Promise

    • 如果x处于pending,promise也需要保持pending,直到x接受pending
    • 如果x处于fulfilled,promise需要使用相同值执行promise
    • 如果x处于rejected,promise需要使用相同的值拒绝promise
  • 如果x是对象或者函数

    • 将x.then赋于then

    • 如果获取x.then时抛出错误,则使用错误作为理由拒绝promise

    • 如果then是函数🤔️

      • 使用x作为then函数的作用域中的this, 传入两个参数作为回调,分别是resolvePromise, 和rejectPromise

      • 如果resolvePromise如果以y为参数运行,则运行[[Resolve]](promise, y), 履行promise。resolve(promise,y)

      • 如果rejectPromise以r为参数运行,则以r为原因拒绝promise。reject(promise, r)

      • 如果then抛出了错误

        • 如果resolvePromise,rejectPromise已经调用对错误忽略
        • 如果没有调用,用错误作为原因拒绝promise
    • 如果then不是函数,使用x作为参数执行promise

  • 如果x不是对象也不是函数,则使用x作为参数执行promise

注解

onFulfilled和onRejected方法应当是异步执行,且应该在then方法被调用的那一轮事件循环之后的微任务执行栈中执行。可以使用宏任务macrotask和微任务microtask机制实现。也就是说宏任务完成后(Promise的then调用后),会清空微任务队列(执行onFulfilled或者onRejected)。

macrotask包含:script(整体代码)、setTimeout、setInterval、I/O、UI交互事件、postMessage 、requestAnimationFrame 、MessageChannel、etImmediate(Node.js 环境)

microtask包含:Promise.then、setImmediate、MutaionObserver、process.nextTick(Node.js 环境)

事件循环

下面👇是一个关于事件循环的例子🌰

image

Promise中的参数函数,应当是立即调用的。我们这时先执行了resolve(1), 这会将外层的Promise的状态置为fulfilled态。但是这时外层的then还没有执行。

我们接下来执行了Promise.resolve().then(() => console.log(2)), 我们将then的onFulfilled,push到了macrotask队列中,会在宏任务执行完成后清空微任务。

执行外层Promise的then, onFulfilled, push到了macrotask队列中。

接着执行console.log(3), 然后清空macrotask队列,执行2,1(队列先进先出)

Promise构造函数


const pending = 0
const fulfilled = 1
const rejected = 2

function reject (promise, result) {}

function resolve (promise, reason) {}

export default class Promise {
  constructor (fn) {
    this.fn = fn
    // Promise的状态, 初始状态为pending
    this._state = pending
    // Promise的fulfilled态的原因,或者Promise的rejected态的理由
    this._value = null
    // 存储then的队列
    this._tasks = []
    // 创建Promise对象后,立即执行fn
    this.init()
  }

  init () {
    try {
      // 执行fn, 传入包装后的resolve,reject
      // reject,resolve会修改promise的状态
      this.fn(
        (result) => resolve(this, result),
        (reason) => reject(this, reason)
      )
    } catch (error) {
      reject(this)
    }
  }
}

根据规范2.2.6: then may be called multiple times on the same promisethen可以在同一个promise中多次调用。所以我们用一个数组存储then的结果。

Promise.prototype.then

根据规范2.2.1: A promise’s then method accepts two arguments. Both onFulfilled and onRejected are optional arguments。then接受两个参数, 两个参数可选。

根据规范2.2.1.1, 2.2.1.2: If onFulfilled is not a function, it must be ignored. If onRejected is not a function, it must be ignored.。onFulfilled, onRejected必须是函数负责会被忽略。

根据规范2.2.2, 2.2.3, onFulfilled必须在状态为fulfilled调用, onRejected必须在状态为rejected调用

根据规范2.2.7: then must return a promise。then必须返回一个promise。

🤔️ 为什么不能返回当前的promise?因为根据规范2.1.2, 2.1.3, promise被修改状态后,不能再次修改状态。我们如果需要执行then的callback需要修改promise的状态。

我们使用task封装📦then的回调以及then返回的promise。


class Task {
  constructor (onFulfilled, onRejected, promise) {
    if (typeof onFulfilled !== 'function') {
      onFulfilled = null
    }
    if (typeof onRejected !== 'function') {
      onRejected = null
    }
    this.onFulfilled = onFulfilled
    this.onRejected = onRejected
    this.promise = promise
  }
}

class Promise {

  // ...

  then (onFulfilled, onRejected) {

    let nextPromise = new Promise(function () {})

    let task = new Task(onFulfilled, onRejected, nextPromise)

    if (this._state === pending) {
      this._tasks.push(task)
    } else {
      handlePromise(this, task)
    }

    // 返回新的promise
    return nextPromise
  }
}

Promise.prototype.catch

catch函数同样会返回新的promise, 但是在创建task时,我们不会传入onFulfilled参数。所以当promise当为fulfilled态,虽然catch的回调同样存放_task中,但是由于callback为null, 在handlePromise中会向下穿透。

class Promise {
  // ...
  catch (onRejected) {
    let nextPromise = new Promise(function () {})

    // onFulfilled设置为null
    let task = new Task(null, onRejected, nextPromise)

    if (this._state === pending) {
      this._tasks.push(task)
    } else {
      handlePromise(this, task)
    }

    // 返回新的promise
    return nextPromise
  }
}

Promise.prototype.finally

finally方法用于指定不管Promise对象最后状态如何,都会执行的操作。

我们无论promise的状态如何,都在promise的最后面添加then,并传入了onFulfilled, onRejected两个回调。在回调中,我们执行finally的callback参数。这样无论之前的promise是fulfilled态,还是rejected态,都会执行finally添加的参数。


class Promise {
  // ...

  finally (callback) {
    // this指向调用finally的对象
    const self = this
    // 向Promise链中添加then,无论,promise是resolve态还是reject态,都会执行callback
    // 并且会通过then,继续将result或reason向下传递
    return self.then(
      result => Promise.resolve(callback()).then(_ => result),
      reason => Promise.resolve(callback()).then(_ => { throw reason })
    )
  }
}

resolve

resolve用来修改promise状态,将promise状态设置为fulfilled态, 并执行then的onFulfilled回调

根据规范**2.3.1: If promise and x refer to the same object, reject promise with a TypeError as the reason.**如果promise与x相等,我们使用TypeError的错误拒绝promise

根据规范2.3.2。如果result是promise,并且处于pending态,promise需要保持pending态,直到result被执行和拒绝后,我们使用result的状态履行或者拒绝promise。如果result是promise,并且处于fulfilled或rejected态,我们使用result的状态拒绝或者履行promise。

根据规范2.3.3, 我们判断result是否为一个Object。如果result为Object, 我们则取出它的then的属性, 判断then属性是否为Function, 如果then为Function, 我们设置then的作用域的this指向result, 我们传入resolvePromise, rejectPromise作为参数。

根据规范2.3.4: If x is not an object or function, fulfill promise with x, 如果x不是函数或者对象,我们用result结果作为参数执行promise。


function resolve (promise, result) {
  if (promise === result) {
    throw reject(promise, new TypeError('promise and x refer to the same object'))
  }

  if (isPromise(result)) {
    if (result._state === pending) {
      result._tasks.concat(promise._tasks)
    } else if (result._state === fulfilled || result._state === rejected) {
      let task
      while (task = promise._tasks.shift()) {
        handlePromise(result, task)
      }
    }
    return
  }

  if (isObject(result)) {

    let then = null

    try {
      then = result.then
    } catch (error) {
      reject(promise, error)
    }

    if (isFunction(then)) {
      try {
        let resolvePromise = function (y) {
          resolve(promise, y)
        }
        let rejectPromise = function (r) {
          reject(promise, r)
        }
        then.call(result, resolvePromise, rejectPromise)
      } catch (error) {
        reject(promise, error)
      }

      return
    }
  }

  promise._state = fulfilled
  promise._value = result

  if (promise._tasks && promise._tasks.length) {
    let task = null
    while (task = promise._tasks.shift()) {
      handlePromise(promise, task)
    }
  }
}

reject

reject将promise的状态设置为rejected, 并以当前的promise的状态,执行promise中通过then注册的onRejected回调。


function reject (promise, reason) {
  if (promise._state !== pending) {
    return
  }

  promise._state = rejected
  promise._value = reason

  let task
  while (task = promise._tasks.shift()) {
    handlePromise(promise, task)
  }
}

handlePromise

handlePromise函数主要根据当前的promise的状态, 以及内容(resolve或者reject的参数)。处理通过then注册的回调。并且会链式的调用,注册在then返回的新promise的上的then的回调


// 将回调的结果,传入第二个then中
fn().then().then()

根据规范2.2.4, 以及规范给出的注解。当promise的状态改变,onFulfilled, onRejected并不会立即执行,而且在本次的宏任务完成后,才会执行onFulfilled或者onRejected。而setImmediate则是将代码push到微任务队列中。在宏任务中会清空微任务队列。


function handlePromise (prevPromise, task) {
  // 需要在宏任务完后的微任务队列中执行
  setImmediate(() => {
    // nextPromise是then返回的promise
    const { onFulfilled, onRejected, promise: nextPromise } = task
    let callback = null

    let value = prevPromise._value
    let state = prevPromise._state

    if (state === fulfilled) {
      callback = onFulfilled
    } else if (state === rejected) {
      callback = onRejected
    }

    if (!callback) {
      // 如果在promise中没有注册callback
      if (state === fulfilled) {
        resolve(nextPromise, value)
      } else if (state === rejected) {
        reject(nextPromise, value)
      }
    } else {
      try {
        const result = callback(value)
        // 对then中返回promise处理
        // 将callback返回的结果,带入到新的promise中
        resolve(nextPromise, result)
      } catch (error) {
        reject(nextPromise, error)
      }
    }
  })
}

Promise.resolve & Promise.reject

Promise.resolve方法返回一个新的Promise对象,状态为resolved。Promise.reject(reason)方法也会返回一个新的 Promise实例,该实例的状态为rejected。

class Promise {

  // ...

  static resolve (result) {
    return new Promise((resolve) => { resolve(result) })
  }

  static reject (reason) {
    return new Promise((_, reject) => { reject(reason) })
  }
}

Promise.all && Promise.race

Promise.all和Promise.race必须接受一个数组为参数,数组中为多个Promise的实例。Promise.all和Promise.race的使用我就不再这里赘述了。

Promise.all会使用计数器,记录Promise数组中的所有Promise实例的状态是否都变为fulfilled态,如果计数器的长度和数组长度一致,我们则会将Promise.all的状态设置为fulfilled态。


class Promise {
  static race (promises) {
    if (isArray(promises)) {
      let promisesLength = promises.length
      return new Promise((resolve, reject) => {
        for (let i = 0; i < promisesLength; i++) {
          promises[i].then((result) => {
            resolve(result)
          }).catch((error) => {
            reject(error)
          })
        }
      })
    } else {
      throw new TypeError('The arguments must be arrays')
    }
  }

  static all (promises) {
    if (isArray(promises)) {
      let promisesLength = promises.length
      let counter = 0
      let resultList = []
      return new Promise((resolve, reject) => {
        for (let i = 0; i < promisesLength; i++) {
          promises[i].then((result) => {
            counter += 1
            resultList.push(result)
            if (counter === promisesLength) {
              resolve(resultList)
            }
          }).catch((error) => {
            reject(error)
          })
        }
      })
    } else {
      throw new TypeError('The arguments must be arrays')
    }
  }
}

其他

VueRouter源码分析

Preact源码分析

[转载]原文链接:https://segmentfault.com/a/1190000018914581

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-62136.html

文章标题:自己鼓捣一个Promise

相关文章
JavaScript使用pop方法移除数组最后一个元素用法实例
本文实例讲述了JavaScript使用pop方法移除数组最后一个元素的用法。分享给大家供大家参考。具体如下: 下面的代码演示了JS数组的pop方法,可以用来移除数组的最后一个元素,实际上就是把数组当成堆栈使用 &lt;!DOCTYPE ht...
2017-03-22
使用iview的组件 Table 表格,有固定列,设置其中一个列适应屏幕大小
描述 在使用iview的组件Table表格时,有固定列,表格列宽不等。 在表格平铺时,不能自适应宽度。 问题 每个列有需要设置的宽度,有固定的列,很难调整某一列的宽度为刚刚好的。此时需要某一列自适应宽度。 解决 &lt;template...
2018-09-28
js日期范围初始化得到前一个月日期的方法
本文实例讲述了js日期范围初始化得到前一个月日期的方法。分享给大家供大家参考。具体分析如下: 今天做时间范围的初始化设定,开始时间是当前时间的前一个月,终于找到完美的解决方案了。 Date.prototype.format = functi...
2017-03-23
JS实现带缓冲效果打开、关闭、移动一个层的方法
本文实例讲述了JS带缓冲效果打开、关闭、移动一个层的方法。分享给大家供大家参考。具体实现方法如下: &lt;!DOCTYPE html PUBLIC &quot;-&#x2F;&#x2F;W3C&#x2F;&#x2F;DTD XHTML 1...
2017-03-23
JavaScript中的Promise使用详解
许多的语言,为了将异步模式处理得更像平常的顺序,都包含一种有趣的方案库,它们被称之为promises,deferreds,或者futures。JavaScript的promises ,可以促进关注点分离,以代替紧密耦合的接口。 本文讲的是基...
2017-03-24
如果我实现了自己的OS,我算开发者中的精英吗?
相信有很多Linux爱好者心中都怀有编出一个自己的操作系统的理想,一位外国网友在quora上提出了这个问题。本期#linuxstory 说#节目翻译节选了一个令人启迪的回答,让我们从另一个角度思考个人重新发明轮子的意义。以下是网友的答案: ...
2015-12-25
JavaScript获取一个范围内日期的方法
本文实例讲述了JavaScript获取一个范围内日期的方法。分享给大家供大家参考。具体分析如下: 指定开始和结束时间,范围该范围内的所有日期放入数组 Date.prototype.addDays = function(days) { v...
2017-03-22
JavaScript将一个数组插入到另一个数组的方法
本文实例讲述了JavaScript将一个数组插入到另一个数组的方法。分享给大家供大家参考。具体分析如下: 这段JS代码可以通过Array.prototype.push.apply方法将一个数组插入到另外一个数组,下面的代码将数组b插入到a ...
2017-03-21
js实现同一个页面多个渐变效果的方法
本文实例讲述了js实现同一个页面多个渐变效果的方法。分享给大家供大家参考。具体分析如下: 这里可实现5个元素中随便一个,鼠标移上去透明度渐渐增加,鼠标移出,透明度渐渐减小的效果。 要点一: var speed = 0; if(target&...
2017-03-22
VUE开发一个图片轮播的组件示例代码
本人刚学习vue,用vue做了个图片轮播,下面我来记录一下,有需要了解VUE开发一个图片轮播的组件的朋友可参考。希望此文章对各位有所帮助。 完成效果图如下: vue开发的思路主要是数据绑定,代码如下: &lt;template&gt; ...
2017-03-14
回到顶部