问题描述
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:
- 承诺状态
then
方法- 承诺解决程序
规范没有提到如何创建、履行或拒绝承诺.
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 withvalue
.reject(reason)
moves the promise from the pending state to the rejected state, with rejection reasonreason
.
这是 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);
}
}
};
}
注意
promise
对象只有一个then
属性,它当前是undefined
.我们仍然需要决定then
函数应该是什么以及承诺对象应该具有哪些其他属性(即承诺对象的形状).此决定还将影响fulfill
和reject
功能的实现.resolve(promise, value)
和reject(promise, value)
函数应该只能调用一次,如果我们调用一个,那么我们就不能调用叫对方.因此,我们将它们封装在一个闭包中,并确保它们在两者之间只被调用一次.- 我们在
deferred
的定义中引入了一个新函数,promise解析过程resolve(promise, value)
.规范将此函数表示为[[Resolve]](promise, x)
.此功能的实现完全由规范规定.因此,我们接下来将实施它.
- The
promise
object only has athen
property which is currentlyundefined
. We still need to decide on what thethen
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 thefulfill
andreject
functions. - The
resolve(promise, value)
andreject(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. - We introduced a new function in the definition of
deferred
, the promise resolution procedureresolve(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);
}
}
注意
- 我们省略了第 2.3.2 节,因为这是一种依赖于 Promise 形状的优化目的.我们将在最后重新访问此部分.
- 如上所示,第 2.3.3.3 节的描述比实际代码长得多.这是因为聪明的 hack
promise = deferred(promise)
允许我们重用deferred
函数的逻辑.这确保了promise.resolve
和promise.reject
在两者之间只能调用一次.我们只需要对deferred
函数做一个小改动,就可以使这个 hack 起作用.
- 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.
- 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 thedeferred
function. This ensures thatpromise.resolve
andpromise.reject
are only callable once between both of them. We only need to make a small change to thedeferred
function to make this hack work.
function deferred(promise) {
var call = true;
promise = promise || {
then: undefined,
...
};
return /* the same object as before */
}
Promise 状态和 then
方法
我们已经将确定承诺对象的形状的问题推迟了很长时间,但我们不能再推迟了,因为 fulfill
和 reject
的实现> 功能取决于它.是时候阅读规范关于承诺状态的内容了:
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.
- 未决时,承诺:
- When pending, a promise:
- 可能会转换为已完成或已拒绝状态.
- 不得转换到任何其他状态.
- 必须有一个不能改变的值.
- 不得转换到任何其他状态.
- 必须有一个不能改变的理由.
这里,不得改变"是指不变的身份(即===
),但并不意味着深度不变性.
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) { ... }
此外,我们还需要一个属性来保存承诺的数据.当承诺挂起时,数据是一个 onFulfilled
和 onRejected
回调队列.当承诺被履行时,数据就是承诺的价值.当承诺被拒绝时,数据就是承诺的原因.
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 对象的形状,我们就可以最终实现 fulfill
和 reject
函数:
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
、fulfilled
和rejected
函数.我们将从 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.
最后,我们实现了fulfilled
和rejected
函数如下:
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+ 规范的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!