像q这样的 promise/defer 库是如何实现的?我试图阅读源代码,但发现它很难理解,所以我认为如果有人能从高层次向我解释一下,在单线程 JS 环境中实现承诺的技术是什么?比如 Node 和浏览器。
5 回答
我发现比展示一个例子更难解释,所以这里有一个非常简单的延迟/承诺的实现。
免责声明:这不是一个功能实现,并且缺少 Promise/A 规范的某些部分,这只是为了解释 Promise 的基础。
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);
}
}
}
推迟:
现在创建一个有 promise 的 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);
});
}
};
请注意,在超时时调用回调以允许代码始终是异步的。
这就是基本的延迟/承诺实现所需要的。
创建类和示例:
现在让我们将两个对象都转换为类,首先是承诺:
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 规范。请注意,主要库(Q、when.js、rsvp.js、node-promise,...)遵循Promises/A规范。
希望我足够清楚。
编辑:
正如评论中所问的,我在这个版本中添加了两件事:
- 调用 then 的可能性,无论它具有什么状态。
- 链接承诺的可能性。
为了能够在解决时调用 Promise,您需要将状态添加到 Promise 中,然后在调用 then 时检查该状态。如果状态已解决或被拒绝,只需使用其数据或错误执行回调。
为了能够链接 Promise,您需要为每次调用生成一个新的 defer,then
并且当 Promise 被解析/拒绝时,使用回调的结果解析/拒绝新的 Promise。所以当 Promise 完成时,如果回调返回一个新的 Promise,它会绑定到返回的 Promise 上then()
。如果不是,则使用回调的结果来解决承诺。
这是承诺:
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);
})
}
};
如您所见,它已经增长了很多。
Q 在实现方面是一个非常复杂的 Promise 库,因为它旨在支持流水线和 RPC 类型的场景。我在这里有我自己的Promises/A+规范的非常简单的实现。
原则上它很简单。在 promise 被解决/解决之前,您通过将它们推送到数组中来记录任何回调或 errbacks。当 Promise 被解决时,你调用适当的回调或 errbacks 并记录 Promise 被解决的结果(以及它是被履行还是被拒绝)。解决后,您只需使用存储的结果调用回调或 errbacks。
这给了你 aproximately 的语义done
。要构建then
,您只需要返回一个新的 Promise,该 Promise 会通过调用回调/errbacks 的结果来解决。
如果您对开发支持 RPC 和流水线(如 Q)的完整 Promise 实现背后的推理的完整解释感兴趣,您可以在此处阅读 kriskowal 的推理。这是一种非常好的渐进式方法,如果您正在考虑实现承诺,我不能高度推荐。即使您只是要使用 Promise 库,它也可能值得一读。
首先确保你理解 Promise 应该如何工作。查看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; }
Promise 有一种
then
方法可以链接它们。我接受一个回调并返回一个新的 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;
};
您可能想查看关于 Adehun的博客文章。
Adehun 是一个非常轻量级的实现(大约 166 LOC),对于学习如何实现 Promise/A+ 规范非常有用。
免责声明:我写了博客文章,但博客文章确实解释了有关 Adehun 的所有内容。
转换功能 - 状态转换的关守
看门人功能;确保在满足所有必需条件时发生状态转换。
如果满足条件,此函数会更新 Promise 的状态和值。然后它触发处理功能进行进一步处理。
流程功能根据转换执行正确的操作(例如,待完成),稍后将解释。
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。两个主要要求:
基本承诺(然后被调用的承诺)需要使用传入的处理程序创建一个新的承诺;base 还存储了对这个创建的 Promise 的内部引用,因此一旦 base 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;
}`
Process 函数 - 处理转换
这在状态转换之后或调用 then 函数时调用。因此它需要检查未决的承诺,因为它可能是从 then 函数调用的。
Process 对所有内部存储的 Promise 运行 Promise Resolution 过程(即通过 then 函数附加到基本 Promise 的那些)并强制执行以下 Promise/A+ 要求:
使用 Utils.runAsync 帮助器(围绕 setTimeout 的薄包装器(setImmediate 也可以工作))异步调用处理程序。
如果 onSuccess 和 onReject 处理程序丢失,则为它们创建后备处理程序。
根据承诺状态选择正确的处理函数,例如完成或拒绝。
将处理程序应用于基本承诺的值。该操作的值被传递给 Resolve 函数以完成 promise 处理周期。
如果发生错误,则附加的 Promise 会立即被拒绝。
function process() { var that = this, fulfillFallBack = function(value) { return value; }, rejectFallBack = function(reason) { throw 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); } });
}
Resolve 函数 - 解决 Promise
这可能是 Promise 实现中最重要的部分,因为它处理 Promise 解析。它接受两个参数——promise 和它的分辨率值。
虽然对各种可能的分辨率值进行了大量检查;有趣的解决方案有两种——涉及传入的 promise 和 thenable(具有 then 值的对象)。
- 传入一个 Promise 值
如果解析值是另一个promise,那么promise 必须采用这个解析值的状态。由于这个分辨率值可以是未决的或已解决的,最简单的方法是将一个新的 then 处理程序附加到分辨率值并处理其中的原始承诺。每当它解决时,原始承诺将被解决或拒绝。
- 传入 thenable 值
这里的问题是 thenable 值的 then 函数必须只调用一次(函数式编程中的 one 包装器的一个很好的用途)。同样,如果 then 函数的检索抛出异常,则承诺将立即被拒绝。
像以前一样, then 函数被调用最终解决或拒绝承诺的函数,但这里的区别是在第一次调用时设置的被调用标志,并且随后的调用是没有操作的。
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);
}
}
Promise 构造函数
这就是把所有东西放在一起的那个。完成和拒绝函数是通过无操作函数来解析和拒绝的语法糖。
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);
});
}
};
我希望这有助于更清楚地了解 promise 的工作方式。