zoukankan      html  css  js  c++  java
  • Promise原理及实现

    Promise标准解读

    1. 只有一个then方法,没有catch,race,all等方法,甚至没有构造函数

      Promise标准中仅指定了Promise对象的then方法的行为,其它一切我们常见的方法/函数都并没有指定,包括catch,race,all等常用方法,甚至也没有指定该如何构造出一个Promise对象,另外then也没有一般实现中(Q, $q等)所支持的第三个参数,一般称onProgress

    2. then方法返回一个新的Promise

      Promise的then方法返回一个新的Promise,而不是返回this。

      promise2 = promise1.then(alert)
      promise2 != promise1 // true
    3. 不同Promise的实现需要可以相互调用(interoperable)

    4. Promise的初始状态为pending,它可以由此状态转换为fulfilled(本文为了一致把此状态叫做resolved)或者rejected,一旦状态确定,就不可以再次转换为其它状态,状态确定的过程称为settle

    一步一步实现一个Promise

    下面我们就来一步一步实现一个Promise

    构造函数

    因为标准并没有指定如何构造一个Promise对象,所以我们同样以目前一般Promise实现中通用的方法来构造一个Promise对象,也是ES6原生Promise里所使用的方式,即:

    // Promise构造函数接收一个executor函数,executor函数执行完同步或异步操作后,调用它的两个参数resolve和reject
    var promise = new Promise(function(resolve, reject) {
      /*
        如果操作成功,调用resolve并传入value
        如果操作失败,调用reject并传入reason
      */
    })

    我们先实现构造函数的框架如下:

    function Promise(executor) {
      var self = this
      self.status = 'pending' // Promise当前的状态
      self.data = undefined  // Promise的值
      self.onResolvedCallback = [] // Promise resolve时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面
      self.onRejectedCallback = [] // Promise reject时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面
    
      executor(resolve, reject) // 执行executor并传入相应的参数
    }

    上面的代码基本实现了Promise构造函数的主体,但目前还有两个问题:

    1. 我们给executor函数传了两个参数:resolve和reject,这两个参数目前还没有定义
    2. executor有可能会出错(throw),类似下面这样,而如果executor出错,Promise应该被其throw出的值reject:

      new Promise(function(resolve, reject) {
        throw 2
      })

    所以我们需要在构造函数里定义resolve和reject这两个函数:

    function Promise(executor) {
      var self = this
      self.status = 'pending' // Promise当前的状态
      self.data = undefined  // Promise的值
      self.onResolvedCallback = [] // Promise resolve时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面
      self.onRejectedCallback = [] // Promise reject时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面
    
      function resolve(value) {
        // TODO
      }
    
      function reject(reason) {
        // TODO
      }
    
      try { // 考虑到执行executor的过程中有可能出错,所以我们用try/catch块给包起来,并且在出错后以catch到的值reject掉这个Promise
        executor(resolve, reject) // 执行executor
      } catch(e) {
        reject(e)
      }
    }

    resolve和reject这两个函数能不能不定义在构造函数里呢?考虑到我们在executor函数里是以resolve(value),reject(reason)的形式调用的这两个函数,而不是以resolve.call(promise, value),reject.call(promise, reason)这种形式调用的,所以这两个函数在调用时的内部也必然有一个隐含的this,也就是说,要么这两个函数是经过bind后传给了executor,要么它们定义在构造函数的内部,使用self来访问所属的Promise对象。所以如果我们想把这两个函数定义在构造函数的外部,确实是可以这么写的:

    function resolve() {
      // TODO
    }
    function reject() {
      // TODO
    }
    function Promise(executor) {
      try {
        executor(resolve.bind(this), reject.bind(this))
      } catch(e) {
        reject.bind(this)(e)
      }
    }

    但是众所周知,bind也会返回一个新的函数,这么一来还是相当于每个Promise对象都有一对属于自己的resolve和reject函数,就跟写在构造函数内部没什么区别了,所以我们就直接把这两个函数定义在构造函数里面了。不过话说回来,如果浏览器对bind的所优化,使用后一种形式应该可以提升一下内存使用效率。

    另外我们这里的实现并没有考虑隐藏this上的变量,这使得这个Promise的状态可以在executor函数外部被改变,在一个靠谱的实现里,构造出的Promise对象的状态和最终结果应当是无法从外部更改的。

    接下来,我们实现resolve和reject这两个函数

     

    function Promise(executor) {
      // ...
    
      function resolve(value) {
        if (self.status === 'pending') {
          self.status = 'resolved'
          self.data = value
          for(var i = 0; i < self.onResolvedCallback.length; i++) {
            self.onResolvedCallback[i](value)
          }
        }
      }
    
      function reject(reason) {
        if (self.status === 'pending') {
          self.status = 'rejected'
          self.data = reason
          for(var i = 0; i < self.onRejectedCallback.length; i++) {
            self.onRejectedCallback[i](reason)
          }
        }
      }
    
      // ...
    }

    基本上就是在判断状态为pending之后把状态改为相应的值,并把对应的value和reason存在self的data属性上面,之后执行相应的回调函数,逻辑很简单,这里就不多解释了。

    then方法

    Promise对象有一个then方法,用来注册在这个Promise状态确定后的回调,很明显,then方法需要写在原型链上。then方法会返回一个Promise,关于这一点,Promise/A+标准并没有要求返回的这个Promise是一个新的对象,但在Promise/A标准中,明确规定了then要返回一个新的对象,目前的Promise实现中then几乎都是返回一个新的Promise对象,所以在我们的实现中,也让then返回一个新的Promise对象。

    关于这一点,我认为标准中是有一点矛盾的:

    标准中说,如果promise2 = promise1.then(onResolved, onRejected)里的onResolved/onRejected返回一个Promise,则promise2直接取这个Promise的状态和值为己用,但考虑如下代码:

    promise2 = promise1.then(function foo(value) {
      return Promise.reject(3)
    })

    此处如果foo运行了,则promise1的状态必然已经确定且为resolved,如果then返回了this(即promise2 === promise1),说明promise2和promise1是同一个对象,而此时promise1/2的状态已经确定,没有办法再取Promise.reject(3)的状态和结果为己用,因为Promise的状态确定后就不可再转换为其它状态。

    另外每个Promise对象都可以在其上多次调用then方法,而每次调用then返回的Promise的状态取决于那一次调用then时传入参数的返回值,所以then不能返回this,因为then每次返回的Promise的结果都有可能不同。

    下面我们来实现then方法:

    // then方法接收两个参数,onResolved,onRejected,分别为Promise成功或失败后的回调
    Promise.prototype.then = function(onResolved, onRejected) {
      var self = this
      var promise2
    
      // 根据标准,如果then的参数不是function,则我们需要忽略它,此处以如下方式处理
      onResolved = typeof onResolved === 'function' ? onResolved : function(v) {}
      onRejected = typeof onRejected === 'function' ? onRejected : function(r) {}
    
      if (self.status === 'resolved') {
        return promise2 = new Promise(function(resolve, reject) {
    
        })
      }
    
      if (self.status === 'rejected') {
        return promise2 = new Promise(function(resolve, reject) {
    
        })
      }
    
      if (self.status === 'pending') {
        return promise2 = new Promise(function(resolve, reject) {
    
        })
      }
    }

    Promise总共有三种可能的状态,我们分三个if块来处理,在里面分别都返回一个new Promise。

    根据标准,我们知道,对于如下代码,promise2的值取决于then里面函数的返回值:

    promise2 = promise1.then(function(value) {
      return 4
    }, function(reason) {
      throw new Error('sth went wrong')
    })

    如果promise1被resolve了,promise2的将被4 resolve,如果promise1被reject了,promise2将被new Error('sth went wrong') reject,更多复杂的情况不再详述。

    所以,我们需要在then里面执行onResolved或者onRejected,并根据返回值(标准中记为x)来确定promise2的结果,并且,如果onResolved/onRejected返回的是一个Promise,promise2将直接取这个Promise的结果:

    Promise.prototype.then = function(onResolved, onRejected) {
      var self = this
      var promise2
    
      // 根据标准,如果then的参数不是function,则我们需要忽略它,此处以如下方式处理
      onResolved = typeof onResolved === 'function' ? onResolved : function(value) {}
      onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {}
    
      if (self.status === 'resolved') {
        // 如果promise1(此处即为this/self)的状态已经确定并且是resolved,我们调用onResolved
        // 因为考虑到有可能throw,所以我们将其包在try/catch块里
        return promise2 = new Promise(function(resolve, reject) {
          try {
            var x = onResolved(self.data)
            if (x instanceof Promise) { // 如果onResolved的返回值是一个Promise对象,直接取它的结果做为promise2的结果
              x.then(resolve, reject)
            }
            resolve(x) // 否则,以它的返回值做为promise2的结果
          } catch (e) {
            reject(e) // 如果出错,以捕获到的错误做为promise2的结果
          }
        })
      }
    
      // 此处与前一个if块的逻辑几乎相同,区别在于所调用的是onRejected函数,就不再做过多解释
      if (self.status === 'rejected') {
        return promise2 = new Promise(function(resolve, reject) {
          try {
            var x = onRejected(self.data)
            if (x instanceof Promise) {
              x.then(resolve, reject)
            }
          } catch (e) {
            reject(e)
          }
        })
      }
    
      if (self.status === 'pending') {
      // 如果当前的Promise还处于pending状态,我们并不能确定调用onResolved还是onRejected,
      // 只能等到Promise的状态确定后,才能确实如何处理。
      // 所以我们需要把我们的**两种情况**的处理逻辑做为callback放入promise1(此处即this/self)的回调数组里
      // 逻辑本身跟第一个if块内的几乎一致,此处不做过多解释
        return promise2 = new Promise(function(resolve, reject) {
          self.onResolvedCallback.push(function(value) {
            try {
              var x = onResolved(self.data)
              if (x instanceof Promise) {
                x.then(resolve, reject)
              }
            } catch (e) {
              reject(e)
            }
          })
    
          self.onRejectedCallback.push(function(reason) {
            try {
              var x = onRejected(self.data)
              if (x instanceof Promise) {
                x.then(resolve, reject)
              }
            } catch (e) {
              reject(e)
            }
          })
        })
      }
    }
    
    // 为了下文方便,我们顺便实现一个catch方法
    Promise.prototype.catch = function(onRejected) {
      return this.then(null, onRejected)
    }

    至此,我们基本实现了Promise标准中所涉及到的内容,但还有几个问题:

    1. 不同的Promise实现之间需要无缝的可交互,即Q的Promise,ES6的Promise,和我们实现的Promise之间以及其它的Promise实现,应该并且是有必要无缝相互调用的,比如:

      // 此处用MyPromise来代表我们实现的Promise
      new MyPromise(function(resolve, reject) { // 我们实现的Promise
        setTimeout(function() {
          resolve(42)
        }, 2000)
      }).then(function() {
        return new Promise.reject(2) // ES6的Promise
      }).then(function() {
        return Q.all([ // Q的Promise
          new MyPromise(resolve=>resolve(8)), // 我们实现的Promise
          new Promise.resolve(9), // ES6的Promise
          Q.resolve(9) // Q的Promise
        ])
      })

      我们前面实现的代码并没有处理这样的逻辑,我们只判断了onResolved/onRejected的返回值是否为我们实现的Promise的实例,并没有做任何其它的判断,所以上面这样的代码目前是没有办法在我们的Promise里正确运行的。

    2. 下面这样的代码目前也是没办法处理的:

      new Promise(resolve=>resolve(8))
        .then()
        .then()
        .then(function foo(value) {
          alert(value)
        })

      正确的行为应该是alert出8,而如果拿我们的Promise,运行上述代码,将会alert出undefined。这种行为称为穿透,即8这个值会穿透两个then(说Promise更为准确)到达最后一个then里的foo函数里,成为它的实参,最终将会alert出8。

    下面我们首先处理简单的情况,值的穿透

    Promise值的穿透

    通过观察,会发现我们希望下面这段代码

    new Promise(resolve=>resolve(8))
      .then()
      .catch()
      .then(function(value) {
        alert(value)
      })

    跟下面这段代码的行为是一样的

    new Promise(resolve=>resolve(8))
      .then(function(value){
        return value
      })
      .catch(function(reason){
        throw reason
      })
      .then(function(value) {
        alert(value)
      })

    所以如果想要把then的实参留空且让值可以穿透到后面,意味着then的两个参数的默认值分别为function(value) {return value},function(reason) {throw reason}。
    所以我们只需要把then里判断onResolved和onRejected的部分改成如下即可:

    onResolved = typeof onResolved === 'function' ? onResolved : function(value) {return value}
    onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {throw reason}

    于是Promise神奇的值的穿透也没有那么黑魔法,只不过是then默认参数就是把值往后传或者抛

    不同Promise的交互

    关于不同Promise间的交互,其实标准里是有说明的,其中详细指定了如何通过then的实参返回的值来决定promise2的状态,我们只需要按照标准把标准的内容转成代码即可。

    这里简单解释一下标准:

    即我们要把onResolved/onRejected的返回值,x,当成一个可能是Promise的对象,也即标准里所说的thenable,并以最保险的方式调用x上的then方法,如果大家都按照标准实现,那么不同的Promise之间就可以交互了。而标准为了保险起见,即使x返回了一个带有then属性但并不遵循Promise标准的对象(比如说这个x把它then里的两个参数都调用了,同步或者异步调用(PS,原则上then的两个参数需要异步调用,下文会讲到),或者是出错后又调用了它们,或者then根本不是一个函数),也能尽可能正确处理。

    最后,我们刚刚说到,原则上,promise.then(onResolved, onRejected)里的这两相函数需要异步调用,关于这一点,标准里也有说明:

    In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack.

    所以我们需要对我们的代码做一点变动,即在四个地方加上setTimeout(fn, 0)。

    事实上,即使不参照标准,最终在自测试时也会发现如果then的参数不以异步的方式调用,有些情况下Promise会不按预期的方式行为,通过不断的自测,最终你必然会让then的参数异步执行,让executor函数立即执行。本人在一开始实现Promise时就没有参照标准,而是自己凭经验测试,最终发现的这个问题。

    至此,我们就实现了一个的Promise,完整代码如下:

    try {
      module.exports = Promise
    } catch (e) {}
    
    function Promise(executor) {
      var self = this
    
      self.status = 'pending'
      self.onResolvedCallback = []
      self.onRejectedCallback = []
    
      function resolve(value) {
        if (value instanceof Promise) {
          return value.then(resolve, reject)
        }
        setTimeout(function() { // 异步执行所有的回调函数
          if (self.status === 'pending') {
            self.status = 'resolved'
            self.data = value
            for (var i = 0; i < self.onResolvedCallback.length; i++) {
              self.onResolvedCallback[i](value)
            }
          }
        })
      }
    
      function reject(reason) {
        setTimeout(function() { // 异步执行所有的回调函数
          if (self.status === 'pending') {
            self.status = 'rejected'
            self.data = reason
            for (var i = 0; i < self.onRejectedCallback.length; i++) {
              self.onRejectedCallback[i](reason)
            }
          }
        })
      }
    
      try {
        executor(resolve, reject)
      } catch (reason) {
        reject(reason)
      }
    }
    
    function resolvePromise(promise2, x, resolve, reject) {
      var then
      var thenCalledOrThrow = false
    
      if (promise2 === x) {
        return reject(new TypeError('Chaining cycle detected for promise!'))
      }
    
      if (x instanceof Promise) {
        if (x.status === 'pending') { //because x could resolved by a Promise Object
          x.then(function(v) {
            resolvePromise(promise2, v, resolve, reject)
          }, reject)
        } else { //but if it is resolved, it will never resolved by a Promise Object but a static value;
          x.then(resolve, reject)
        }
        return
      }
    
      if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
        try {
          then = x.then //because x.then could be a getter
          if (typeof then === 'function') {
            then.call(x, function rs(y) {
              if (thenCalledOrThrow) return
              thenCalledOrThrow = true
              return resolvePromise(promise2, y, resolve, reject)
            }, function rj(r) {
              if (thenCalledOrThrow) return
              thenCalledOrThrow = true
              return reject(r)
            })
          } else {
            resolve(x)
          }
        } catch (e) {
          if (thenCalledOrThrow) return
          thenCalledOrThrow = true
          return reject(e)
        }
      } else {
        resolve(x)
      }
    }
    
    Promise.prototype.then = function(onResolved, onRejected) {
      var self = this
      var promise2
      onResolved = typeof onResolved === 'function' ? onResolved : function(v) {
        return v
      }
      onRejected = typeof onRejected === 'function' ? onRejected : function(r) {
        throw r
      }
    
      if (self.status === 'resolved') {
        return promise2 = new Promise(function(resolve, reject) {
          setTimeout(function() { // 异步执行onResolved
            try {
              var x = onResolved(self.data)
              resolvePromise(promise2, x, resolve, reject)
            } catch (reason) {
              reject(reason)
            }
          })
        })
      }
    
      if (self.status === 'rejected') {
        return promise2 = new Promise(function(resolve, reject) {
          setTimeout(function() { // 异步执行onRejected
    try {
              var x = onRejected(self.data)
              resolvePromise(promise2, x, resolve, reject)
            } catch (reason) {
              reject(reason)
            }
          })
        })
      }
    
      if (self.status === 'pending') {
        // 这里之所以没有异步执行,是因为这些函数必然会被resolve或reject调用,而resolve或reject函数里的内容已是异步执行,构造函数里的定义
    return promise2 = new Promise(function(resolve, reject) {
          self.onResolvedCallback.push(function(value) {
            try {
              var x = onResolved(value)
              resolvePromise(promise2, x, resolve, reject)
            } catch (r) {
              reject(r)
            }
          })
    
          self.onRejectedCallback.push(function(reason) {
              try {
                var x = onRejected(reason)
                resolvePromise(promise2, x, resolve, reject)
              } catch (r) {
                reject(r)
              }
            })
        })
      }
    }
    
    Promise.prototype.catch = function(onRejected) {
      return this.then(null, onRejected)
    }
    
    Promise.deferred = Promise.defer = function() {
      var dfd = {}
      dfd.promise = new Promise(function(resolve, reject) {
        dfd.resolve = resolve
        dfd.reject = reject
      })
      return dfd
    }
  • 相关阅读:
    multiprocessing 源码解析 更新中......
    loadrunner 更新中......
    Java IO
    echarts水球图小数点不显示问题+组件默认值
    双柱表格组件
    表格生成后修改echarts图表样式
    vue中引入单张图片+两张壁纸手动切换
    配置全局组件
    vue使用babel-plugin-import按需引入Ant Design View + babel-plugin-component按需引入element-ui
    vue深浅拷贝的思索
  • 原文地址:https://www.cnblogs.com/zzalmo/p/11286653.html
Copyright © 2011-2022 走看看