zoukankan      html  css  js  c++  java
  • 如何手把手实现一个 Promise/A+

    一直就想手把手实现一下 Promise/A+ 了,碰巧最近在读已逝的 avalon 作者 司徒正美 的书《JavaScript框架设计》,算是奠念前辈吧。因此,程序员。除了有理性和清晰的逻辑思维,还要有强壮灵活身体思想。看书有感记,以此共勉。

    Talk is cheap, show me the code.

    1. 内部通过单向链表结果存储事件成功处理函数、事件失败处理函数,和链表中下一个 Promise 类型对象
    2. Promise 内部实例状态标识:Pending(初始状态)、Fulfilled(成功状态)、Rejected(失败状态)。且状态为单方向移动的
    3. Promise 实例的成功/失败事件函数是基于Promise的状态而被调用的
    4. Promise 总是返回值是一个新的 promise 实例
       var p = Promise.resolve(0)
       var p1 = p.then((value) => value)
       p === p1 // false
    

    PS: 原著中使用 ES4 编写,我使用 ES6 改写。后续有时间补上测试用例。

    /**
     * References:
     * 1. 司徒正美《JavaScript框架设计》
     * 2. [MDN MessageChannel](https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel)
     * 3. [MDN MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver)
     * 3. [Can i use](https://caniuse.com/)
     */
    const REJECTED = "rejected"
    const PENDING = "pending"
    const RESOLVED = "resolved"
    
    class Promise {
      static [Symbol.toStringTag] = "Promise"
    
      /**
       * @constructor
       * @callback Executer
       * @param {(resolve: (value) => {}, reject: (reason) => {} )} executer
       * @param {} executer
       * @returns {Promise<any>}
       */
      constructor(executer) {
        if (!executer || typeof executer !== "function") {
          throw new TypeError(
            `Promise resolver ${typeof executer} is not a function`
          )
        }
    
        // Promise 内部实例状态标识:Pending(初始状态)、Fulfilled(成功状态)、Rejected(失败状态)。且状态为单方向移动的
        this.status = PENDING
        this.value = undefined
    
        // 单向链表结果存储事件成功处理函数、事件失败处理函数,和链表中下一个 Promise 类型对象
        this._deferred = [
          /* nextPromiseInstance?: [
            onResolved: Function,
            onRejected: Function,
            resolve: Function,
            reject: Function] */
        ]
    
        // Promise 实例化的回调函数被同步/立即执行
        executer(
          /* onResolve */ (value) => this._resolve(value),
          /* onReject */ (reason) => this._reject(reason)
        )
      }
    
      // when onFulfilled
      /**
       * Promise 总是返回值是一个新的 promise 实例
       * @param {(value) => {}} onResolved
       * @param {(reason) => {}} onRejected
       */
      then(onResolved, onRejected) {
        return new Promise((resolve, reject) => {
          this._deferred.push([onResolved, onRejected, resolve, reject])
          this._notify()
        })
      }
    
      // when onReject
      /**
       * Promise 总是返回值是一个新的 promise 实例
       * @param {(reason) => {}} onReject
       */
      catch(onReject) {
        return this.then(undefined, onReject)
      }
    
      // finally function
      /**
       * Promise 总是返回值是一个新的 promise 实例
       * @param {Function} callback
       */
      finally(callback) {
        return new Promise(() => {
          if (typeof callback === "function") {
            callback()
          }
        })
      }
    
      /**
       * 内部实现 _resolve 方法,更新 status 到 RESOLVED 并下发 _notify
       * @param {Promise|any} value
       */
      _resolve(value) {
        if (this.status === PENDING) {
          if (value === this) {
            throw new TypeError(`Promise settled with itself.`)
          }
    
          let called = false
    
          try {
            const then = value && value["then"]
    
            // if it is a Promise or Thenable object
            // so executes it and pass its value to
            // the current promise's resolve/reject
            if (
              value !== null &&
              typeof value === "object" &&
              typeof then === "function" &&
              value instanceof Promise
            ) {
              then.call(
                value,
                function onResolve(value) {
                  if (!called) {
                    this._resolve(value)
                  }
    
                  called = true
                },
                function onReject(reason) {
                  if (!called) {
                    this._reject(value)
                  }
    
                  called = false
                }
              )
    
              return
            }
          } catch (error) {
            if (!called) {
              this._reject(error)
            }
            called = true
            return
          }
    
          this.status = RESOLVED
          this.value = value
          this._notify()
        }
      }
    
      /**
       * 内部实现 _reject 方法,更新 status 到 REJECTED 并下发 _notify
       * @param {Error|string} reason
       */
      _reject(reason) {
        if (this.status !== PENDING) return
    
        this.status = REJECTED
        this.value = reason
        this._notify()
      }
    
      /**
       * 内部实现 _notify 方法,以微/宏任务执行按序执行单向链表中的 Promise
       */
      _notify() {
        nextTick(() => {
          if (this.status !== PENDING) {
            while (this._deferred.length) {
              const deferred = this._deferred.shift()
              const [onResolved, onRejected, resolve, reject] = deferred
    
              try {
                if (this.status === RESOLVED) {
                  if (typeof onResolved === "function") {
                    resolve(onResolved.call(undefined, this.value))
                  } else {
                    resolve(this.value)
                  }
                } else if (this.status === REJECTED) {
                  if (typeof onRejected === "function") {
                    resolve(onRejected.call(undefined, this.value))
                  } else {
                    reject(this.value)
                  }
                }
              } catch (error) {
                reject(error)
              }
            }
          }
        })
      }
    
      static resolve(value) {
        return new Promise((resolve, _reject) => {
          resolve(value)
        })
      }
    
      static reject(reason) {
        return new Promise((_resolve, reject) => {
          reject(new Error(reason))
        })
      }
    
      static all() {}
      static race() {}
    
      // todo: what `allSettled` means?
      static allSettled() {}
    }
    
    /**
     * 以微/宏任务的方式运行回调,实际上它有个 callback 参数,很多 library 都有实现类似的方法
     * @see {@link https://github.com/vuejs/vue/blob/52719ccab8/src/core/util/next-tick.js How Vue.js implements nextTick?}
     * 
     * 这是其实现前的注释:
    // Technically setImmediate should be the ideal choice, but it's only available
    // in IE. The only polyfill that consistently queues the callback after all DOM
    // events triggered in the same loop is by using MessageChannel.
     */
    function nextTick() {
      // if it runs in nodeJS can use process.nextTick as well
      // const nextTick = inNodeJS && process && process.nextTick
      // return nextTick
    
      /**
       * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate}
       * @see {@link https://caniuse.com/#search=setImmediate}
       * Non-standard, it only works for IE10,IE10+
       *
       * This method is used to break up long running operations and run a callback function immediately
       * after the browser has completed other operations such as events and display updates.
       */
      if (window.setImmediate) {
        return window.setImmediate.bind(window)
      }
    
      const callbacks = []
      function flushCallbacks() {
        const copies = callbacks.slice(0)
        callbacks.length = 0
    
        for (let i = 0; i < copies.length; i++) {
          copies[i]()
        }
      }
    
      /**
       * 所以此处加上我们的 MessageChannel 实现
       * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel}
       * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MessagePort}
       * @see {@link https://caniuse.com/#search=MessageChannel}
       *
       * The MessageChannel interface of the Channel Messaging API allows us to create a
       * new message channel and send data through it via its two MessagePort properties.
       */
      if (window.MessageChannel) {
        const channel = new MessageChannel()
        // Returns port2 of the channel.
        const port = channel.port2
        channel.port1.onmessage = flushCallbacks
    
        return function (fn) {
          // push callback function into
          callbacks.push(fn)
          port.postMessage(1)
        }
      }
    
      /**
       * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver}
       * @see {@link https://caniuse.com/#search=MutationObserver}
       * Works on IE10+ and all modern browsers.
       *
       * The MutationObserver interface provides the ability to watch for changes being made to the DOM tree.
       * It is designed as a replacement for the older Mutation Events feature,
       * which was part of the DOM3 Events specification.
       */
      if (window.MutationObserver) {
        const node = document.createTextNode("promise")
    
        // Create an observer instance linked to the callback function
        new MutationObserver(flushCallbacks).observe(
          /* Select the node that will be observed for mutations */
          node,
          /* Options for the observer (which mutations to observe) */
          {
            characterData: true,
          }
        )
        let bool = false
    
        return function (fn) {
          callbacks.push(fn)
          bool = !bool
          node.data = bool
        }
      }
    
      // jQuery Deferred Object 实现中的 next_fast_way_image
      // 另一实现是 next_fast_way_state_change 跟 image 类似
      // 不过是通过动态插入一段 script 监听 onStateChange 实现
      if (window.Image) {
        const image = new Image()
        image.onload = image.onerror = flushCallbacks
    
        return function (fn) {
          callbacks.push(fn)
          image.src = "data:image/png," + Math.random()
        }
      }
    
      // 最终回退到 setTimeout
      return function (fn) {
        const timerId = setTimeout(() => {
          clearTimeout(timerId)
          fn()
        }, 4)
      }
    }
    
    nextTick = nextTick()
    
    
  • 相关阅读:
    ccBPM典型的树形表单和多表头表单的流程示例
    Arrays -数组工具类,数组转化字符串,数组排序等
    String
    ArrayList
    Random
    Scanner
    Phone-java标准类
    HelloWorld-java
    c++ 由无向图构造邻接表,实现深度优先遍历、广度优先遍历。
    c++实现哈夫曼树,哈夫曼编码,哈夫曼解码(字符串去重,并统计频率)
  • 原文地址:https://www.cnblogs.com/givingwu/p/12776363.html
Copyright © 2011-2022 走看看