什么是承诺?
promise 是一个thenable,其行为符合Promises/A+ 规范。
thenable 是具有then 方法的任何对象或函数。
这是一个承诺的样子:
var promise = {
...
then: function (onFulfilled, onRejected) { ... },
...
};
这是我们从一开始就知道的关于承诺的唯一事情(不包括它的行为)。
了解 Promises/A+ 规范
Promises/A+ 规范分为 3 个主要部分:
- 承诺状态
-
then 方法
- 承诺解决程序
规范没有提到如何创建、履行或拒绝承诺。
因此,我们将从创建这些函数开始:
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 来创建新的 Promise:
这是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);
}
}
};
}
注意
-
promise 对象只有一个then 属性,当前为undefined。我们仍然需要决定then 函数应该是什么以及promise 对象应该具有哪些其他属性(即promise 对象的形状)。这一决定还将影响fulfill 和reject 函数的实现。
-
resolve(promise, value) 和reject(promise, value) 函数只能调用一次,如果我们调用其中一个,就不能调用另一个。因此,我们将它们包装在一个闭包中,并确保它们在它们之间只被调用一次。
- 我们在
deferred 的定义中引入了一个新函数,即承诺解析过程resolve(promise, value)。规范将此函数表示为[[Resolve]](promise, x)。此功能的实现完全由规范规定。因此,我们接下来会实现它。
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);
}
}
注意
- 我们省略了section 2.3.2,因为它是一种取决于promise 对象形状的优化。我们将在接近尾声时重温本节。
- 如上所见,section 2.3.3.3 的描述比实际代码要长得多。这是因为巧妙的 hack
promise = deferred(promise) 允许我们重用 deferred 函数的逻辑。这确保了promise.resolve 和promise.reject 在它们之间只能调用一次。我们只需要对 deferred 函数做一点小改动就可以让这个 hack 发挥作用。
function deferred(promise) {
var call = true;
promise = promise || {
then: undefined,
...
};
return /* the same object as before */
}
Promise 状态和then 方法
我们已经将决定 Promise 对象形状的问题推迟了很长时间,但我们不能再拖延了,因为 fulfill 和 reject 函数的实现都依赖于它。是时候阅读规范中关于承诺状态的内容了:
promise 必须处于以下三种状态之一:待处理、已完成或已拒绝。
- 待处理时,承诺:
- 可能会转换为已完成或已拒绝状态。
- 当实现时,一个承诺:
- 不得转换到任何其他状态。
- 必须有一个不能改变的值。
- 当被拒绝时,一个承诺:
- 不得转换到任何其他状态。
- 必须有原因,不能改变。
在这里,“不得更改”是指不可变的身份(即===),但并不意味着深度不变。
我们如何知道 Promise 当前处于哪个状态?我们可以这样做:
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 方法观察到(即,根据promise 的状态,then 方法的行为不同),我们可以创建三个专门的then 函数对应于这三种状态:
var promise = {
then: pending | fulfilled | rejected,
...
};
function pending(onFulfilled, onRejected) { ... }
function fulfilled(onFulfilled, onRejected) { ... }
function rejected(onFulfilled, onRejected) { ... }
此外,我们还需要一个属性来保存 Promise 的数据。当承诺待处理时,数据是onFulfilled 和onRejected 回调的队列。当承诺兑现时,数据就是承诺的价值。当 promise 被拒绝时,数据就是 promise 的原因。
当我们创建一个新的 Promise 时,初始状态是挂起的,初始数据是一个空队列。因此,我们可以如下完成deferred函数的实现:
function deferred(promise) {
var call = true;
promise = promise || {
then: pending,
data: []
};
return /* the same object as before */
}
此外,现在我们知道了 Promise 对象的形状,我们终于可以实现 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);
}
我们需要使用setTimeout,因为根据规范onFulfilled 或onRejected 的section 2.2.4,在执行上下文堆栈仅包含平台代码之前不得调用。
接下来,我们需要实现pending、fulfilled和rejected函数。我们将从pending 函数开始,它将onFulfilled 和onRejected 回调推送到队列并返回一个新的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 是否是函数,因为根据规范的section 2.2.1,它们是可选参数。如果提供了onFulfilled 和onRejected,则它们按照规范的section 2.2.7.1 和section 2.2.7.2 与延迟值组合。否则按照规范的section 2.2.7.3 和section 2.2.7.4 短路。
最后,我们实现fulfilled和rejected函数如下:
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 are monads 可以在上面恰当命名的 bind 函数中看到。至此,我们的 Promises/A+ 规范的实现就完成了。
优化resolve
规范的Section 2.3.2 描述了当x 被确定为承诺时对resolve(promise, 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) {
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 的形式提供。您可以简单地下载它并运行测试套件:
$ npm install promises-aplus-tests -g
$ promises-aplus-tests promise.js
不用说,所有的测试都通过了。