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

查看:129
本文介绍了理解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

显然,承诺/ 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.

推荐答案

什么是承诺?



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

What is a promise?

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

A thenable 是任何具有的对象或函数一个然后方法。

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. 然后方法

  3. 承诺解决程序

  1. Promise states
  2. The then Method
  3. The Promise Resolution Procedure

规范未提及如何创建,履行或拒绝承诺。

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}组成的对象


  • 承诺是一个当前处于待定状态的承诺。

  • resolve(value)使用解析承诺。

  • 拒绝(原因)将承诺从待处理状态转移到被拒绝状态,拒绝原因原因

  • 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 function:

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

NB


  1. 承诺对象只有然后属性,这是目前 undefined 。我们仍然需要决定然后函数应该是什么以及promise对象应具有的其他属性(即promise对象的形状)。此决定还将影响履行拒绝函数的实施。

  2. 解析(承诺,价值)拒绝(承诺,价值)函数应该只能调用一次,如果我们打电话给我们然后我们不应该打电话给对方。因此,我们将它们包装在一个闭包中,并确保它们只在它们之间被调用一次。

  3. 我们在 deferred的定义中引入了一个新函数,承诺解决程序 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);
    }
}

NB


  1. 我们省略了第2.3.2节因为它是一个取决于promise对象形状的优化。我们将在最后重新审视此部分。

  2. 如上所述, 2.3.3.3节比实际代码长得多。这是因为聪明的黑客 promise = deferred(promise)这允许我们重用 deferred 函数的逻辑。这可以确保 promise.resolve promise.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 */
}



< h3>承诺状态和然后方法

我们推迟了决定承诺对象形状的问题这么久但我们不能再拖延了,因为履行拒绝函数的实现依赖于它。现在是时候阅读规范对承诺状态的说法了:

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:


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

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. 必须有一个原因,不得改变。


  • 在这里,“一定不要改变rdquo;意味着不可变的身份(即 === ),但并不意味着深刻的不变性。

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

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

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

    然而,还有更好的选择。由于承诺的状态只能通过它的然后方法观察(即取决于承诺的状态然后方法表现不同),我们可以创建三个专门的然后函数对应三种状态:

    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) { ... }
    

    此外,我们还需要一个属性来保存承诺的数据。当承诺未决时,数据是 onFulfilled onRejected 回调的队列。当履行承诺时,数据是承诺的价值。当promise被拒绝时,数据就是promise的原因。

    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对象的形状,我们终于可以实现履行拒绝函数:

    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节规范 onFulfilled onRejected

    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.

    接下来,我们需要实现待定履行拒绝函数。我们将从 pending 函数开始,该函数将 onFulfilled 推送到onRejected 回调队列并返回一个新的承诺:

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

    我们需要测试 onFulfilled onRejected 是函数,因为根据第2.2节。规范1 它们是可选参数。如果提供 onFulfilled onRejected ,则根据第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.

    最后,我们实施履行被拒绝的功能如下:

    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是monad ,可以在上面恰当命名的 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.

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

    If promises are monads then they must have a unit function as well. Surprisingly, promises have two unit functions: one for a resolved promise and one for a rejected promise. Since the tests require them anyway we'll add them to the exports interface:

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



    优化解决



    规范的第2.3.2节描述了<的优化当$ x 被确定为一个承诺时,code> resolve(promise,x)函数。这是优化的 resolve 函数:

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

    请注意,虽然规格说明了If / when已完成 x ,履行承诺具有相同的价值”但我们必须使用 resolve 而不是履行。我不确定这是否是规范中的错误。

    Note that although the specification says “If/when x is fulfilled, fulfill promise with the same value” yet we must use resolve instead of fulfill. I'm not sure whether or not that's a mistake in the specification.

    代码以要点的形式提供。你可以简单地下载并运行测试套件:

    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天全站免登陆