理解 Promises/A+ 规范 [英] Understanding the Promises/A+ specification

查看:18
本文介绍了理解 Promises/A+ 规范的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Promises/A+ 规范是最小的规范之一.因此,实施它是理解它的最佳方式.Forbes Lindesay 的以下回答引导我们完成 Promises/A+ 规范的实施过程,基本 Javascript 承诺实施尝试.但是,当我测试时,结果并不令人满意:

The Promises/A+ specification is one of the smallest specifications. Hence, implementing it is the best way to understand it. The following answer by Forbes Lindesay walks us through the process of implementing the Promises/A+ specification, Basic Javascript promise implementation attempt. However, when I tested it the results were not satisfactory:

✔ 109 tests passed
✘ 769 tests failed

显然,Promises/A+ 规范并不像看起来那么容易实现.您将如何实现规范并向新手解释您的代码?Forbes Lindesay 在解释他的代码方面做得非常出色,但不幸的是他的实现是不正确的.

Clearly, the Promises/A+ specification is not as easy to implement as it seems. How would you implement the specification and explain your code to a novice? Forbes Lindesay does an excellent job explaining his code but unfortunately his implementation is incorrect.

推荐答案

什么是承诺?

promise 是一个 thenable,其行为符合 Promises/A+ 规范.

What is a promise?

A promise is a thenable whose behavior conforms to the Promises/A+ specification.

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

A thenable is any object or function that has a then method.

以下是承诺的样子:

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

这是我们从一开始就知道的关于承诺的唯一事情(不包括它的行为).

This is the only thing we know about a promise from the outset (excluding its behavior).

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

The Promises/A+ specification is divided into 3 main parts:

  1. 承诺状态
  2. then 方法
  3. 承诺解决程序

规范没有提到如何创建、履行或拒绝承诺.

The specification does not mention how to create, fulfill or reject promises.

因此,我们将从创建这些函数开始:

Hence, we'll start by creating those functions:

function deferred() { ... } // returns an object { promise, resolve, reject }

function fulfill(promise, value) { ... } // fulfills promise with value
function reject(promise, reason) { ... } // rejects promise with reason

虽然没有创建承诺的标准方法,但测试要求我们无论如何都要公开一个 deferred 函数.因此,我们将只使用 deferred 来创建新的承诺:

Although there's no standard way of creating a promise yet the tests require us to expose a deferred function anyway. Hence, we'll only use deferred to create new promises:

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

  • deferred(): creates an object consisting of { promise, resolve, reject }:

  • promise 是当前处于待定状态的承诺.
  • resolve(value) 使用 value 解析承诺.
  • reject(reason) 将承诺从待定状态移动到拒绝状态,拒绝原因为 reason.
  • promise is a promise that is currently in the pending state.
  • resolve(value) resolves the promise with value.
  • reject(reason) moves the promise from the pending state to the rejected state, with rejection reason reason.

这是 deferred 函数的部分实现:

Here's a partial implementation of the deferred function:

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);
            }
        }
    };
}

注意

  1. promise 对象只有一个 then 属性,它当前是 undefined.我们仍然需要决定 then 函数应该是什么以及承诺对象应该具有哪些其他属性(即承诺对象的形状).此决定还将影响 fulfillreject 功能的实现.
  2. resolve(promise, value)reject(promise, value) 函数应该只能调用一次,如果我们调用一个,那么我们就不能调用叫对方.因此,我们将它们封装在一个闭包中,并确保它们在两者之间只被调用一次.
  3. 我们在deferred的定义中引入了一个新函数,promise解析过程resolve(promise, value).规范将此函数表示为 [[Resolve]](promise, x).此功能的实现完全由规范规定.因此,我们接下来将实施它.
  1. The promise object only has a then property which is currently undefined. We still need to decide on what the then function should be and what other properties a promise object should have (i.e. the shape of a promise object). This decision will also affect the implementation of the fulfill and reject functions.
  2. The resolve(promise, value) and reject(promise, value) functions should only be callable once and if we call one then we shouldn't be able to call the other. Hence, we wrap them in a closure and ensure that they are only called once between both of them.
  3. We introduced a new function in the definition of deferred, the promise resolution procedure resolve(promise, value). The specification denotes this function as [[Resolve]](promise, x). The implementation of this function is entirely dictated by the specification. Hence, we'll implement it next.

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);
    }
}

注意

  1. 我们省略了第 2.3.2 节,因为这是一种依赖于 Promise 形状的优化目的.我们将在最后重新访问此部分.
  2. 如上所示,第 2.3.3.3 节的描述比实际代码长得多.这是因为聪明的 hack promise = deferred(promise) 允许我们重用 deferred 函数的逻辑.这确保了 promise.resolvepromise.reject 在两者之间只能调用一次.我们只需要对 deferred 函数做一个小改动,就可以使这个 hack 起作用.
  1. We omitted section 2.3.2 because it's an optimization that depends upon the shape of a promise object. We'll revisit this section towards the end.
  2. As seen above, the description of section 2.3.3.3 is much longer than the actual code. This is because of the clever hack promise = deferred(promise) which allows us to reuse the logic of the deferred function. This ensures that promise.resolve and promise.reject are only callable once between both of them. We only need to make a small change to the deferred function to make this hack work.

function deferred(promise) {
    var call = true;

    promise = promise || {
        then: undefined,
        ...
    };

    return /* the same object as before */
}

Promise 状态和 then 方法

我们已经将确定承诺对象的形状的问题推迟了很长时间,但我们不能再推迟了,因为 fulfillreject 的实现> 功能取决于它.是时候阅读规范关于承诺状态的内容了:

Promise states and the then method

We've delayed the problem of deciding the shape of a promise object for so long but we can't delay any further because the implementations of both the fulfill and reject functions depend upon it. It's time to read what the specification has to say about promise states:

promise 必须处于以下三种状态之一:pending、fulfiled 或 denied.

A promise must be in one of three states: pending, fulfilled, or rejected.

  1. 未决时,承诺:
  1. When pending, a promise:
  1. 可能会转换为已完成或已拒绝状态.

  • 实现时,承诺:

  • When fulfilled, a promise:

    1. 不得转换到任何其他状态.
    2. 必须有一个不能改变的值.

  • 当被拒绝时,一个承诺:

  • When rejected, a promise:

    1. 不得转换到任何其他状态.
    2. 必须有一个不能改变的理由.

  • 这里,不得改变"是指不变的身份(即===),但并不意味着深度不变性.

    Here, "must not change" means immutable identity (i.e. ===), but does not imply deep immutability.

    我们如何知道 Promise 当前处于哪种状态?我们可以这样做:

    How do we know which state the promise is currently in? We could do something like this:

    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
        ...
    };
    

    不过,还有更好的选择.由于承诺的状态只能通过它的 then 方法观察(即根据承诺的状态,then 方法的行为不同),我们可以创建三个专门的 then 三个状态对应的函数:

    However, there's a better alternative. Since the state of a promise is only observable through it's then method (i.e. depending upon the state of the promise the then method behaves differently), we can create three specialized then functions corresponding to the three states:

    var promise = {
        then: pending | fulfilled | rejected,
        ...
    };
    
    function pending(onFulfilled, onRejected) { ... }
    function fulfilled(onFulfilled, onRejected) { ... }
    function rejected(onFulfilled, onRejected) { ... }
    

    此外,我们还需要一个属性来保存承诺的数据.当承诺挂起时,数据是一个 onFulfilledonRejected 回调队列.当承诺被履行时,数据就是承诺的价值.当承诺被拒绝时,数据就是承诺的原因.

    In addition, we need one more property to hold the data of the promise. When the promise is pending the data is a queue of onFulfilled and onRejected callbacks. When the promise is fulfilled the data is the value of the promise. When the promise is rejected the data is the reason of the promise.

    当我们创建一个新的 promise 时,初始状态是待定的,初始数据是一个空队列.因此,我们可以如下完成deferred函数的实现:

    When we create a new promise the initial state is pending and the initial data is an empty queue. Hence, we can complete the implementation of the deferred function as follows:

    function deferred(promise) {
        var call = true;
    
        promise = promise || {
            then: pending,
            data: []
        };
    
        return /* the same object as before */
    }
    

    此外,既然我们知道了 promise 对象的形状,我们就可以最终实现 fulfillreject 函数:

    In addition, now that we know the shape of a promise object we can finally implement the fulfill and reject functions:

    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);
    }
    

    我们需要使用 setTimeout 因为根据 第 2.2.4 节 在执行上下文堆栈仅包含平台代码之前,不得调用 onFulfilledonRejected 规范.

    We need to use setTimeout because according to section 2.2.4 of the specification onFulfilled or onRejected must not be called until the execution context stack contains only platform code.

    接下来,我们需要实现pendingfulfilledrejected 函数.我们将从 pending 函数开始,该函数将 onFulfilledonRejected 回调推送到队列并返回一个新的承诺:

    Next, we need to implement the pending, fulfilled and rejected functions. We'll start with the pending function which pushes the onFulfilled and onRejected callbacks to the queue and returns a new promise:

    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);
            }
        };
    }
    

    我们需要测试 onFulfilledonRejected 是否是函数,因为根据 第 2.2.1 节 它们是可选参数.如果提供了 onFulfilledonRejected,那么它们将按照 第 2.2.7.1 节第 2.2.7.2 节.否则,它们会根据 第 2.2.7.3 节规范的第 2.2.7.4 节.

    We need to test whether onFulfilled and onRejected are functions because according to section 2.2.1 of the specification they are optional arguments. If onFulfilled and onRejected are provided then they are composed with the deferred value as per section 2.2.7.1 and section 2.2.7.2 of the specification. Otherwise, they are short-circuited as per section 2.2.7.3 and section 2.2.7.4 of the specification.

    最后,我们实现了fulfilledrejected函数如下:

    Finally, we implement the fulfilled and rejected functions as follows:

    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 是 monads,正如在上面恰当命名的 bind 函数中所见.至此,我们对 Promises/A+ 规范的实现现已完成.

    Interestingly, promises are monads as can be seen in the aptly named bind function above. With this, our implementation of the Promises/A+ specification is now complete.

    第 2.3.2 节 描述了对 resolve(promise, x) 函数,当 x 被确定为承诺时.这是优化后的 resolve 函数:

    Section 2.3.2 of the specification describes an optimization for the resolve(promise, x) function when x is determined to be a promise. Here's the optimized resolve function:

    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) {
                fulfill(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 fulfill(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);
        }
    }
    

    把它们放在一起

    代码可作为 gist 使用.您可以简单地下载它并运行测试套件:

    Putting it all together

    The code is available as a gist. You can simply download it and run the test suite:

    $ npm install promises-aplus-tests -g
    $ promises-aplus-tests promise.js
    

    不用说,所有的测试都通过了.

    Needless to say, all the tests pass.

    这篇关于理解 Promises/A+ 规范的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆