理解Promises / A +规范

时间:2016-03-24 03:22:57

标签: javascript promise

Promises/A+规范是最小的规范之一。因此,实施它是理解它的最佳方式。 Forbes Lindesay的以下答案向我们介绍了实施Promises / A +规范Basic Javascript promise implementation attempt的过程。但是,当我tested时,results并不令人满意:

✔ 109 tests passed
✘ 769 tests failed

显然,Promises / A +规范并不像看起来那么容易实现。您将如何实现规范并向新手解释您的代码?福布斯林德赛在解释他的代码方面表现出色,但不幸的是他的实施不正确。

1 个答案:

答案 0 :(得分:10)

什么是承诺?

承诺是thenable,其行为符合Promises/A+规范。

thenable是具有then方法的任何对象或函数。

这是承诺的样子:

var promise = {
    ...
    then: function (onFulfilled, onRejected) { ... },
    ...
};

这是我们从一开始就对承诺的唯一了解(不包括其行为)。

了解Promises / A +规范

Promises / A +规范分为3个主要部分:

  1. 承诺国家
  2. then方法
  3. 承诺解决程序
  4. 规范未提及如何创建,履行或拒绝承诺。

    因此,我们首先要创建这些功能:

    function deferred() { ... } // returns an object { promise, resolve, reject }
    
    function fulfill(promise, value) { ... } // fulfills promise with value
    function reject(promise, reason) { ... } // rejects promise with reason
    

    虽然没有标准的方式来创建承诺,但tests要求我们公开deferred函数。因此,我们只会使用deferred来创建新的承诺:

    • deferred():创建一个由{ promise, resolve, reject }组成的对象:

      • promise是一个当前处于待定状态的承诺。
      • resolve(value)使用value解析了承诺。
      • reject(reason)将保证从待处理状态移至被拒绝状态,拒绝原因为reason

    这是deferred功能的部分实现:

    function deferred() {
        var call = true;
    
        var promise = {
            then: undefined,
            ...
        };
    
        return {
            promise: promise,
            resolve: function (value) {
                if (call) {
                    call = false;
                    resolve(promise, value);
                }
            },
            reject: function (reason) {
                if (call) {
                    call = false;
                    reject(promise, reason);
                }
            }
        };
    }
    

    <强> N.B。

    1. promise对象只有then属性,当前为undefined。我们仍然需要决定then函数应该是什么以及promise对象应该具有的其他属性(即promise对象的形状)。此决定还将影响fulfillreject功能的实施。
    2. resolve(promise, value)reject(promise, value)函数只能调用一次,如果我们调用一个函数,那么我们就不能调用另一个函数。因此,我们将它们包装在一个闭包中,并确保它们只在它们之间被调用一次。
    3. 我们在deferred的定义中引入了一个新函数,即承诺解析程序resolve(promise, value)。规范将此函数表示为[[Resolve]](promise, x)。该功能的实现完全由规范决定。因此,我们接下来会实施它。
    4. function resolve(promise, x) {
      // 2.3.1. If promise and x refer to the same object,
      //        reject promise with a TypeError as the reason.
          if (x === promise) return reject(promise, new TypeError("Self resolve"));
      // 2.3.4. If x is not an object or function, fulfill promise with x.
          var type = typeof x;
          if (type !== "object" && type !== "function" || x === null)
              return fulfill(promise, x);
      // 2.3.3.1. Let then be x.then.
      // 2.3.3.2. If retrieving the property x.then results in a thrown exception e,
      //          reject promise with e as the reason.
          try {
              var then = x.then;
          } catch (e) {
              return reject(promise, e);
          }
      // 2.3.3.4. If then is not a function, fulfill promise with x.
          if (typeof then !== "function") return fulfill(promise, x);
      // 2.3.3.3. If then is a function, call it with x as this, first argument
      //          resolvePromise, and second argument rejectPromise, where:
      // 2.3.3.3.1. If/when resolvePromise is called with a value y,
      //            run [[Resolve]](promise, y).
      // 2.3.3.3.2. If/when rejectPromise is called with a reason r,
      //            reject promise with r.
      // 2.3.3.3.3. If both resolvePromise and rejectPromise are called,
      //            or multiple calls to the same argument are made,
      //            the first call takes precedence, and any further calls are ignored.
      // 2.3.3.3.4. If calling then throws an exception e,
      // 2.3.3.3.4.1. If resolvePromise or rejectPromise have been called, ignore it.
      // 2.3.3.3.4.2. Otherwise, reject promise with e as the reason.
          promise = deferred(promise);
          try {
              then.call(x, promise.resolve, promise.reject);
          } catch (e) {
              promise.reject(e);
          }
      }
      

      <强> N.B。

      1. 我们省略了section 2.3.2,因为它是一个取决于promise对象形状的优化。我们将在最后重新审视此部分。
      2. 如上所述,section 2.3.3.3的描述比实际代码长得多。这是因为聪明的hack promise = deferred(promise)允许我们重用deferred函数的逻辑。这可确保promise.resolvepromise.reject只能在它们之间调用一次。我们只需要对deferred函数进行一些小改动就可以使这个hack工作。
      3. function deferred(promise) {
            var call = true;
        
            promise = promise || {
                then: undefined,
                ...
            };
        
            return /* the same object as before */
        }
        

        承诺状态和then方法

        我们已经延迟了决定承诺对象形状的问题已经延迟了很长时间但我们无法进一步推迟,因为fulfillreject函数的实现都是如此依靠它。现在是时候阅读规范对承诺状态所说的内容了:

          

        承诺必须处于以下三种状态之一:待处理,履行或拒绝。

             
            
        1. 等待时,承诺:      
              
          1. 可能会转换为已履行或已拒绝的状态。
          2.   
        2.   
        3. 满足后,承诺:      
              
          1. 不得过渡到任何其他州。
          2.   
          3. 必须有一个值,不得更改。
          4.   
        4.   
        5. 被拒绝时,承诺:      
              
          1. 不得过渡到任何其他州。
          2.   
          3. 必须有理由,不得改变。
          4.   
        6.         

          在这里,“绝不能改变”意味着不可变的身份(即===),但并不意味着深刻的不变性。

        我们如何知道目前承诺的状态?我们可以这样做:

        var PENDING   = 0;
        var FULFILLED = 1;
        var REJECTED  = 2;
        
        var promise = {
            then:  function (onFulfilled, onRejected) { ... },
            state: PENDING | FULFILLED | REJECTED, // vertical bar is not bitwise or
            ...
        };
        

        但是,这是一个更好的选择。由于promise的状态只能通过它的then方法观察(即取决于then方法的行为方式不同的承诺状态),我们可以创建三个专门的{{1}对应于三种状态的函数:

        then

        此外,我们还需要一个属性来保存承诺的数据。当承诺未决时,数据是var promise = { then: pending | fulfilled | rejected, ... }; function pending(onFulfilled, onRejected) { ... } function fulfilled(onFulfilled, onRejected) { ... } function rejected(onFulfilled, onRejected) { ... } onFulfilled回调的队列。当履行承诺时,数据是承诺的价值。当承诺被拒绝时,数据就是承诺的原因。

        当我们创建一个新的promise时,初始状态是挂起的,初始数据是一个空队列。因此,我们可以按如下方式完成onRejected函数的实现:

        deferred

        此外,既然我们知道了promise对象的形状,我们最终可以实现function deferred(promise) { var call = true; promise = promise || { then: pending, data: [] }; return /* the same object as before */ } fulfill函数:

        reject

        我们需要使用function fulfill(promise, value) { setTimeout(send, 0, promise.data, "onFulfilled", value); promise.then = fulfilled; promise.data = value; } function reject(promise, reason) { setTimeout(send, 0, promise.data, "onRejected", reason); promise.then = rejected; promise.data = reason; } function send(queue, callback, data) { for (var item of queue) item[callback](data); } 因为根据规范的section 2.2.4 setTimeoutonFulfilled,在执行上下文堆栈仅包含平台代码之前不得调用。

        接下来,我们需要实施onRejectedpendingfulfilled函数。我们将从rejected函数开始,该函数将pendingonFulfilled回调推送到队列并返回一个新的承诺:

        onRejected

        我们需要测试function pending(onFulfilled, onRejected) { var future = deferred(); this.data.push({ onFulfilled: typeof onFulfilled === "function" ? compose(future, onFulfilled) : future.resolve, onRejected: typeof onRejected === "function" ? compose(future, onRejected) : future.reject }); return future.promise; } function compose(future, fun) { return function (data) { try { future.resolve(fun(data)); } catch (reason) { future.reject(reason); } }; } onFulfilled是否为函数,因为根据规范的section 2.2.1,它们是可选参数。如果提供了onRejectedonFulfilled,那么它们将根据规范的section 2.2.7.1section 2.2.7.2由延迟值组成。否则,它们会按照规范的section 2.2.7.3section 2.2.7.4进行短路。

        最后,我们按如下方式实施onRejectedfulfilled函数:

        rejected

        有趣的是,promises are monads可以在上面恰当命名的function fulfilled(onFulfilled, onRejected) { return bind(this, onFulfilled); } function rejected(onFulfilled, onRejected) { return bind(this, onRejected); } function bind(promise, fun) { if (typeof fun !== "function") return promise; var future = deferred(); setTimeout(compose(future, fun), 0, promise.data); return future.promise; } 函数中看到。有了这个,我们对Promises / A +规范的实现现已完成。

        bind函数

        如果promises是monad,那么它们也必须具有unit函数。令人惊讶的是,承诺有两个单元功能:一个用于解决的承诺,一个用于拒绝承诺。由于tests无论如何都需要它们,我们会将它们添加到导出界面:

        unit

        优化exports.resolved = function (data) { return { then: fulfilled, data: data }; }; exports.rejected = function (data) { return { then: rejected, data: data }; }; exports.deferred = deferred;

        规范的

        Section 2.3.2描述了当resolve被确定为承诺时resolve(promise, x)函数的优化。这是优化的x函数:

        resolve

        请注意,虽然规范中说“如果/ function resolve(promise, x) { if (x === promise) return reject(promise, new TypeError("Self resolve")); var type = typeof x; if (type !== "object" && type !== "function" || x === null) return fulfill(promise, x); try { var then = x.then; } catch (e) { return reject(promise, e); } if (typeof then !== "function") return fulfill(promise, x); // 2.3.2.1. If x is pending, promise must remain pending until x is // fulfilled or rejected. if (then === pending) return void x.data.push({ onFulfilled: function (value) { resolve(promise, value); }, onRejected: function (reason) { reject(promise, reason); } }); // 2.3.2.2. If/when x is fulfilled, fulfill promise with the same value. if (then === fulfilled) return resolve(promise, x.data); // 2.3.2.3. If/when x is rejected, reject promise with the same reason. if (then === rejected) return reject(promise, x.data); promise = deferred(promise); try { then.call(x, promise.resolve, promise.reject); } catch (e) { promise.reject(e); } } 已满足,但请使用相同的价值x”,但我们必须使用promise而不是resolve。我不确定这是否是规范中的错误。

        全部放在一起

        代码以gist的形式提供。您只需下载并运行测试套件即可:

        fulfill

        毋庸置疑,所有测试都通过了。