如何实现像q这样的promise / defer库?我试图阅读源代码,但发现它很难理解,所以我认为如果有人可以从较高层次上向我解释在单线程JS环境中实现promise的技术,那就太好了。例如Node和浏览器。
Answers:
我发现比说明示例更难解释了,因此这是延迟/承诺的一种非常简单的实现。
免责声明:这不是功能性实现,并且Promise / A规范的某些部分丢失了,这只是在解释承诺的基础。
tl; dr:转到“创建类和示例”部分以查看完整的实现。
首先,我们需要创建一个带有回调数组的Promise对象。我将开始处理对象,因为它更清晰:
var promise = {
callbacks: []
}
现在使用以下方法添加回调:
var promise = {
callbacks: [],
then: function (callback) {
callbacks.push(callback);
}
}
我们也需要错误回调:
var promise = {
okCallbacks: [],
koCallbacks: [],
then: function (okCallback, koCallback) {
okCallbacks.push(okCallback);
if (koCallback) {
koCallbacks.push(koCallback);
}
}
}
现在创建将具有承诺的defer对象:
var defer = {
promise: promise
};
延迟需要解决:
var defer = {
promise: promise,
resolve: function (data) {
this.promise.okCallbacks.forEach(function(callback) {
window.setTimeout(function () {
callback(data)
}, 0);
});
},
};
并需要拒绝:
var defer = {
promise: promise,
resolve: function (data) {
this.promise.okCallbacks.forEach(function(callback) {
window.setTimeout(function () {
callback(data)
}, 0);
});
},
reject: function (error) {
this.promise.koCallbacks.forEach(function(callback) {
window.setTimeout(function () {
callback(error)
}, 0);
});
}
};
请注意,在超时中将调用回调,以使代码始终异步。
这就是基本的延迟/承诺实现所需要的。
现在让我们将两个对象都转换成类,首先是promise:
var Promise = function () {
this.okCallbacks = [];
this.koCallbacks = [];
};
Promise.prototype = {
okCallbacks: null,
koCallbacks: null,
then: function (okCallback, koCallback) {
okCallbacks.push(okCallback);
if (koCallback) {
koCallbacks.push(koCallback);
}
}
};
现在推迟:
var Defer = function () {
this.promise = new Promise();
};
Defer.prototype = {
promise: null,
resolve: function (data) {
this.promise.okCallbacks.forEach(function(callback) {
window.setTimeout(function () {
callback(data)
}, 0);
});
},
reject: function (error) {
this.promise.koCallbacks.forEach(function(callback) {
window.setTimeout(function () {
callback(error)
}, 0);
});
}
};
这是一个使用示例:
function test() {
var defer = new Defer();
// an example of an async call
serverCall(function (request) {
if (request.status === 200) {
defer.resolve(request.responseText);
} else {
defer.reject(new Error("Status code was " + request.status));
}
});
return defer.promise;
}
test().then(function (text) {
alert(text);
}, function (error) {
alert(error.message);
});
如您所见,基本部分既简单又小巧。当您添加其他选项(例如多重承诺解决方案)时,它会增加:
Defer.all(promiseA, promiseB, promiseC).then()
或承诺链接:
getUserById(id).then(getFilesByUser).then(deleteFile).then(promptResult);
要阅读有关规范的更多信息:CommonJS Promise Specification。请注意,主要库(Q,when.js,rsvp.js,node-promise等)遵循Promises / A规范。
希望我足够清楚。
如评论中的要求,我在此版本中添加了两件事:
为了能够在解决时调用promise,您需要将状态添加到promise,然后在调用then时检查该状态。如果状态已解决或被拒绝,则只需使用其数据或错误执行回调。
为了能够链接承诺,您需要为每次调用生成一个新的defer,then
并在解决/拒绝承诺时,使用回调的结果来解决/拒绝新的promise。因此,在完成承诺后,如果回调函数返回新的承诺,则该绑定到与一起返回的承诺then()
。如果不是,则使用回调结果来解决promise。
这是承诺:
var Promise = function () {
this.okCallbacks = [];
this.koCallbacks = [];
};
Promise.prototype = {
okCallbacks: null,
koCallbacks: null,
status: 'pending',
error: null,
then: function (okCallback, koCallback) {
var defer = new Defer();
// Add callbacks to the arrays with the defer binded to these callbacks
this.okCallbacks.push({
func: okCallback,
defer: defer
});
if (koCallback) {
this.koCallbacks.push({
func: koCallback,
defer: defer
});
}
// Check if the promise is not pending. If not call the callback
if (this.status === 'resolved') {
this.executeCallback({
func: okCallback,
defer: defer
}, this.data)
} else if(this.status === 'rejected') {
this.executeCallback({
func: koCallback,
defer: defer
}, this.error)
}
return defer.promise;
},
executeCallback: function (callbackData, result) {
window.setTimeout(function () {
var res = callbackData.func(result);
if (res instanceof Promise) {
callbackData.defer.bind(res);
} else {
callbackData.defer.resolve(res);
}
}, 0);
}
};
和推迟:
var Defer = function () {
this.promise = new Promise();
};
Defer.prototype = {
promise: null,
resolve: function (data) {
var promise = this.promise;
promise.data = data;
promise.status = 'resolved';
promise.okCallbacks.forEach(function(callbackData) {
promise.executeCallback(callbackData, data);
});
},
reject: function (error) {
var promise = this.promise;
promise.error = error;
promise.status = 'rejected';
promise.koCallbacks.forEach(function(callbackData) {
promise.executeCallback(callbackData, error);
});
},
// Make this promise behave like another promise:
// When the other promise is resolved/rejected this is also resolved/rejected
// with the same data
bind: function (promise) {
var that = this;
promise.then(function (res) {
that.resolve(res);
}, function (err) {
that.reject(err);
})
}
};
如您所见,它已经增长了很多。
then
返回另一个Promise(这是必不可少的)。
就实现而言,Q是一个非常复杂的Promise库,因为它旨在支持流水线和RPC类型的方案。我在这里有Promises / A +规范的基本实现。
原则上,这很简单。在兑现/解决承诺之前,您可以通过将所有回调或错误回调推入数组来保留它们的记录。兑现诺言后,您将调用适当的回调或错误回复,并记录兑现诺言的结果(以及实现还是拒绝)。解决之后,您只需使用存储的结果调用回调或错误回调即可。
这使您接近的语义done
。要构建then
,只需要返回一个新的promise,并通过调用回调/ errbacks即可解决。
如果您对全面支持承诺实现的背后开发的全部理由感兴趣,该实现支持像Q这样的RPC和流水线,则可以在此处阅读kriskowal的推理。这是一种非常不错的渐进方法,如果您正在考虑兑现承诺,我就不能推荐它。即使您只是要使用Promise库,也可能值得一读。
正如福布斯(Forbes)在他的回答中提到的,我记述了制作像Q这样的库所涉及的许多设计决策,请参见https://github.com/kriskowal/q/tree/v1/design。可以说,promise库有多个级别,并且许多库在各个级别处停止。
在第一层,由Promises / A +规范捕获,promise是最终结果的代理,并且适合于管理“本地异步”。也就是说,它适合于确保工作以正确的顺序进行,并且适合于简单而直接地聆听操作的结果,而不管其是否已经解决或将来会发生。这也使一个或多个参与方订阅最终结果变得一样简单。
正如我已经实现的那样,Q提供的承诺是最终,远程或最终+远程结果的代理。为此,它的设计是倒置的,对诺言有不同的实现方式-延后的诺言,已兑现的诺言,拒绝的诺言和对远程对象的诺言(最后一个在Q-Connection中实现)。它们都共享相同的接口,并通过发送和接收消息(例如“ then”(对于Promises / A +足够))以及“ get”和“ invoke”来工作。因此,Q与“分布式异步”有关,并且存在于另一层。
但是,Q实际上是从更高的层取下来的,在那层层,promise用于管理分布式异步 相互可疑的各方(例如您,商人,银行,Facebook,政府)-不是敌人,甚至是朋友,但有时存在冲突利益。我实现的Q设计为与强化的安全承诺(这是分隔promise
和的原因resolve
)兼容的API ,希望它将人们引入承诺,培训他们使用此API并允许他们获取其代码如果他们将来需要在安全的mashup中使用Promise。
当然,在层上移动时通常需要在速度上进行权衡。因此,promise实现也可以设计为共存。这就是“ thenable”概念的来源。可以将每一层的Promise库设计为使用来自任何其他层的Promise,因此可以实现多种实现并存,并且用户只能购买所需的东西。
综上所述,没有难读的借口。Domenic和我正在开发一个Q的版本,该版本将更加模块化和易用,其一些分散注意力的依赖性和变通办法已移至其他模块和软件包中。值得庆幸的是,像《福布斯》,克罗克福德等人,通过简化图书馆来填补了教育空白。
首先,请确保您了解Promises应该如何工作。看一下CommonJs Promises提案和Promises / A +规范。
可以用几行简单的代码就可以实现两个基本概念:
Promise确实会异步解决结果。添加回调是透明的操作-与是否已经解决了承诺无关,一旦可用,它们就会被调用。
function Deferred() {
var callbacks = [], // list of callbacks
result; // the resolve arguments or undefined until they're available
this.resolve = function() {
if (result) return; // if already settled, abort
result = arguments; // settle the result
for (var c;c=callbacks.shift();) // execute stored callbacks
c.apply(null, result);
});
// create Promise interface with a function to add callbacks:
this.promise = new Promise(function add(c) {
if (result) // when results are available
c.apply(null, result); // call it immediately
else
callbacks.push(c); // put it on the list to be executed later
});
}
// just an interface for inheritance
function Promise(add) {
this.addCallback = add;
}
承诺有一种then
允许链接它们的方法。我接受了一个回调并返回了一个新的Promise,该Promise将在使用第一个promise的结果被调用后用该回调的结果来解决。如果回调返回一个Promise,它将被同化而不是嵌套。
Promise.prototype.then = function(fn) {
var dfd = new Deferred(); // create a new result Deferred
this.addCallback(function() { // when `this` resolves…
// execute the callback with the results
var result = fn.apply(null, arguments);
// check whether it returned a promise
if (result instanceof Promise)
result.addCallback(dfd.resolve); // then hook the resolution on it
else
dfd.resolve(result); // resolve the new promise immediately
});
});
// and return the new Promise
return dfd.promise;
};
进一步的概念将是维护单独的错误状态(带有附加的回调)并在处理程序中捕获异常,或者保证回调的异步性。添加这些后,您将获得功能齐全的Promise实现。
这是写出的错误内容。不幸的是,它很重复。您可以通过使用额外的闭包来做得更好,但是真的很难理解。
function Deferred() {
var callbacks = [], // list of callbacks
errbacks = [], // list of errbacks
value, // the fulfill arguments or undefined until they're available
reason; // the error arguments or undefined until they're available
this.fulfill = function() {
if (reason || value) return false; // can't change state
value = arguments; // settle the result
for (var c;c=callbacks.shift();)
c.apply(null, value);
errbacks.length = 0; // clear stored errbacks
});
this.reject = function() {
if (value || reason) return false; // can't change state
reason = arguments; // settle the errror
for (var c;c=errbacks.shift();)
c.apply(null, reason);
callbacks.length = 0; // clear stored callbacks
});
this.promise = new Promise(function add(c) {
if (reason) return; // nothing to do
if (value)
c.apply(null, value);
else
callbacks.push(c);
}, function add(c) {
if (value) return; // nothing to do
if (reason)
c.apply(null, reason);
else
errbacks.push(c);
});
}
function Promise(addC, addE) {
this.addCallback = addC;
this.addErrback = addE;
}
Promise.prototype.then = function(fn, err) {
var dfd = new Deferred();
this.addCallback(function() { // when `this` is fulfilled…
try {
var result = fn.apply(null, arguments);
if (result instanceof Promise) {
result.addCallback(dfd.fulfill);
result.addErrback(dfd.reject);
} else
dfd.fulfill(result);
} catch(e) { // when an exception was thrown
dfd.reject(e);
}
});
this.addErrback(err ? function() { // when `this` is rejected…
try {
var result = err.apply(null, arguments);
if (result instanceof Promise) {
result.addCallback(dfd.fulfill);
result.addErrback(dfd.reject);
} else
dfd.fulfill(result);
} catch(e) { // when an exception was re-thrown
dfd.reject(e);
}
} : dfd.reject); // when no `err` handler is passed then just propagate
return dfd.promise;
};
addCallback
,Promise
类中的方法将被多次调用?该then
方法将仅返回一个新Promise
实例,那么为什么要在Deferred
类中保留一个回调数组?
您可能想查看有关Adehun的博客文章。
Adehun是一种非常轻量级的实现(大约166 LOC),对于学习如何实现Promise / A +规范非常有用。
免责声明:我写了博客文章,但博客文章确实解释了有关Adehun的全部内容。
过渡功能–状态过渡的关守
关守功能;确保在满足所有必需条件时发生状态转换。
如果满足条件,此功能将更新承诺的状态和价值。然后,它触发过程功能以进行进一步处理。
过程功能根据转换(例如,待完成)执行正确的操作,稍后将进行说明。
function transition (state, value) {
if (this.state === state ||
this.state !== validStates.PENDING ||
!isValidState(state)) {
return;
}
this.value = value;
this.state = state;
this.process();
}
然后功能
then函数接受两个可选参数(onFulfill和onReject处理程序),并且必须返回一个新的Promise。两个主要要求:
基本的promise(然后被称为基础promise)需要使用传入的处理程序创建一个新的promise;基本承诺还存储了对此创建的承诺的内部引用,因此一旦基本承诺实现/拒绝,就可以调用它。
如果基本诺言得以履行(即履行或拒绝),则应立即调用适当的处理程序。Adehun.js通过在then函数中调用process来处理这种情况。
``
function then(onFulfilled, onRejected) {
var queuedPromise = new Adehun();
if (Utils.isFunction(onFulfilled)) {
queuedPromise.handlers.fulfill = onFulfilled;
}
if (Utils.isFunction(onRejected)) {
queuedPromise.handlers.reject = onRejected;
}
this.queue.push(queuedPromise);
this.process();
return queuedPromise;
}`
流程功能–处理过渡
状态转换后或调用then函数时将调用此方法。因此,由于可能已从then函数调用了它,因此需要检查未完成的promise。
Process对所有内部存储的承诺(即通过then函数附加到基本承诺的承诺)运行Promise Resolution过程,并执行以下Promise / A +要求:
使用Utils.runAsync帮助程序(围绕setTimeout的精简包装(setImmediate也将起作用))异步调用处理程序。
如果缺少onSuccess和onReject处理程序,则为其创建后备处理程序。
根据承诺状态(例如已实现或拒绝)选择正确的处理函数。
将处理程序应用于基本承诺的价值。该操作的值传递给Resolve函数以完成promise处理周期。
如果发生错误,则附加的承诺将立即被拒绝。
function process(){var that = this,performFallBack = function(value){返回值;},rejectFallBack = function(reason){抛出原因;};
if (this.state === validStates.PENDING) {
return;
}
Utils.runAsync(function() {
while (that.queue.length) {
var queuedP = that.queue.shift(),
handler = null,
value;
if (that.state === validStates.FULFILLED) {
handler = queuedP.handlers.fulfill ||
fulfillFallBack;
}
if (that.state === validStates.REJECTED) {
handler = queuedP.handlers.reject ||
rejectFallBack;
}
try {
value = handler(that.value);
} catch (e) {
queuedP.reject(e);
continue;
}
Resolve(queuedP, value);
}
});
}
解决功能–解决承诺
这可能是Promise实现中最重要的部分,因为它可以处理Promise解决方案。它接受两个参数-Promise及其解析值。
尽管要检查各种可能的分辨率值;有趣的解决方案有两种-涉及传入的承诺和thenable(具有then值的对象)。
如果解决方案值是另一个承诺,则该承诺必须采用此解决方案值的状态。由于此解决方案值可以挂起或结算,因此最简单的方法是将新的then处理程序附加到该解决方案值并处理其中的原始承诺。无论何时达成,最初的承诺都会被解决或拒绝。
这里的问题是,thenable值的then函数必须仅被调用一次(对于函数编程的一次包装器来说,这是很好的用法)。同样,如果对then函数的检索抛出异常,则将立即拒绝诺言。
像以前一样,then函数将使用最终解析或拒绝promise的函数来调用,但此处的区别是在第一个调用上设置的被调用标志,而随后的调用则变为无操作。
function Resolve(promise, x) {
if (promise === x) {
var msg = "Promise can't be value";
promise.reject(new TypeError(msg));
}
else if (Utils.isPromise(x)) {
if (x.state === validStates.PENDING){
x.then(function (val) {
Resolve(promise, val);
}, function (reason) {
promise.reject(reason);
});
} else {
promise.transition(x.state, x.value);
}
}
else if (Utils.isObject(x) ||
Utils.isFunction(x)) {
var called = false,
thenHandler;
try {
thenHandler = x.then;
if (Utils.isFunction(thenHandler)){
thenHandler.call(x,
function (y) {
if (!called) {
Resolve(promise, y);
called = true;
}
}, function (r) {
if (!called) {
promise.reject(r);
called = true;
}
});
} else {
promise.fulfill(x);
called = true;
}
} catch (e) {
if (!called) {
promise.reject(e);
called = true;
}
}
}
else {
promise.fulfill(x);
}
}
无极的建设者
这是将所有内容整合在一起的方式。履行和拒绝功能是语法糖,通过无操作功能来解决和拒绝。
var Adehun = function (fn) {
var that = this;
this.value = null;
this.state = validStates.PENDING;
this.queue = [];
this.handlers = {
fulfill : null,
reject : null
};
if (fn) {
fn(function (value) {
Resolve(that, value);
}, function (reason) {
that.reject(reason);
});
}
};
我希望这有助于对诺言的工作方式有更多的了解。