Aad*_*hah 1 javascript promise
的承诺/ A +规范是最小规范之一.因此,实施它是理解它的最佳方式.Forbes Lindesay的以下答案向我们介绍了实现Promises/A +规范,Basic Javascript promise实现尝试的过程.但是,当我测试它时,结果并不令人满意:
? 109 tests passed
? 769 tests failed
Run Code Online (Sandbox Code Playgroud)
很明显,Promises/A +规范并不像看起来那么容易实现.您将如何实现规范并向新手解释您的代码?Forbes Lindesay在解释他的代码方面表现非常出色,但遗憾的是他的实现不正确.
Aad*_*hah 11
promise是一种thenable符合Promises/A +规范的行为.
A thenable是具有then方法的任何对象或函数.
这是承诺的样子:
var promise = {
...
then: function (onFulfilled, onRejected) { ... },
...
};
Run Code Online (Sandbox Code Playgroud)
这是我们从一开始就了解承诺的唯一事情(不包括其行为).
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
Run Code Online (Sandbox Code Playgroud)
虽然没有标准的创建承诺的方法,但测试要求我们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);
}
}
};
}
Run Code Online (Sandbox Code Playgroud)
NB
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);
}
}
Run Code Online (Sandbox Code Playgroud)
NB
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 */
}
Run Code Online (Sandbox Code Playgroud)
then方法我们已经延迟了长期决定promise对象形状的问题,但是我们不能再拖延了,因为它们fulfill和reject函数的实现都依赖于它.是时候阅读规范对承诺状态的说法了:
承诺必须处于以下三种状态之一:待处理,履行或拒绝.
- 等待时,承诺:
- 可以过渡到已履行或被拒绝的状态.
- 满足后,承诺:
- 不得过渡到任何其他州.
- 必须有一个值,不能改变.
- 被拒绝时,承诺:
- 不得过渡到任何其他州.
- 必须有一个不能改变的理由.
在这里,"绝不能改变"意味着不可改变的身份(即
===),但并不意味着深刻的不变性.
我们如何知道目前承诺的状态?我们可以这样做:
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
...
};
Run Code Online (Sandbox Code Playgroud)
但是,有一个更好的选择.由于promise的状态只能通过它的then方法观察(即取决于then方法行为不同的promise的状态),我们可以创建三个then对应于三种状态的专用函数:
var promise = {
then: pending | fulfilled | rejected,
...
};
function pending(onFulfilled, onRejected) { ... }
function fulfilled(onFulfilled, onRejected) { ... }
function rejected(onFulfilled, onRejected) { ... }
Run Code Online (Sandbox Code Playgroud)
此外,我们还需要一个属性来保存承诺的数据.当许未决的数据是队列onFulfilled和onRejected回调.当履行承诺时,数据是承诺的价值.当承诺被拒绝时,数据就是承诺的原因.
当我们创建一个新的promise时,初始状态是挂起的,初始数据是一个空队列.因此,我们可以deferred按如下方式完成该功能的实现:
function deferred(promise) {
var call = true;
promise = promise || {
then: pending,
data: []
};
return /* the same object as before */
}
Run Code Online (Sandbox Code Playgroud)
另外,既然我们知道了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);
}
Run Code Online (Sandbox Code Playgroud)
我们需要使用setTimeout因为根据规范的第2.2.4节,onFulfilled或者onRejected在执行上下文堆栈仅包含平台代码之前不得调用.
接下来,我们需要实现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);
}
};
}
Run Code Online (Sandbox Code Playgroud)
我们需要测试是否onFulfilled和onRejected是函数,因为根据2.2.1节的规范它们是可选的参数.如果onFulfilled并且onRejected提供了它们,则根据2.2.7.1 节和规范的2.2.7.2 节,它们由延迟值组成.否则,它们被短路按照部分2.2.7.3和节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;
}
Run Code Online (Sandbox Code Playgroud)
有趣的是,承诺是monad,可以在bind上面恰当命名的函数中看到.有了这个,我们现在完成了Promises/A +规范的实现.
unit功能如果承诺是monad,那么它们也必须具有一个unit功能.令人惊讶的是,承诺有两个单元功能:一个用于解决的承诺,一个用于拒绝承诺.由于测试需要它们,我们将它们添加到导出界面:
exports.resolved = function (data) {
return {
then: fulfilled,
data: data
};
};
exports.rejected = function (data) {
return {
then: rejected,
data: data
};
};
exports.deferred = deferred;
Run Code Online (Sandbox Code Playgroud)
resolve规范的2.3.2节描述了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);
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,虽然规范说"如果/何时x满足,但要满足promise相同的值",我们必须使用resolve而不是fulfill.我不确定这是否是规范中的错误.
该代码可作为要点提供.您只需下载并运行测试套件即可:
$ npm install promises-aplus-tests -g
$ promises-aplus-tests promise.js
Run Code Online (Sandbox Code Playgroud)
不用说,所有的测试都通过了.
| 归档时间: |
|
| 查看次数: |
1837 次 |
| 最近记录: |