承诺/推迟图书馆如何实施?

像q这样的承诺/推迟库如何实现? 我正在尝试阅读源代码,但发现它很难理解,所以我认为如果有人能够从高层向我解释什么是在单线程JS环境中实现promise的技术像Node和浏览器。

我觉得很难解释比展示一个例子,所以这是一个非常简单的延迟/承诺的实现。

免责声明:这不是function实现,Promise / A规范的某些部分不存在,这只是为了解释承诺的基础。

tl; dr:转到创build类和示例部分以查看完整的实现。

诺言:

首先,我们需要用一个callback数组创build一个promise对象。 我将开始使用对象,因为它更清晰:

var promise = { callbacks: [] } 

现在使用该方法添加callback:

 var promise = { callbacks: [], then: function (callback) { callbacks.push(callback); } } 

我们也需要错误callback:

 var promise = { okCallbacks: [], koCallbacks: [], then: function (okCallback, koCallback) { okCallbacks.push(okCallback); if (koCallback) { koCallbacks.push(koCallback); } } } 

推迟:

现在创build将有承诺的延期对象:

 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); }); } }; 

请注意,callback在超时中被调用以允许代码始终是asynchronous的。

这就是基本的延期/承诺实施所需要的。

创build类和示例:

现在让我们把这两个对象转换成类,首先是承诺:

 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); }); 

正如你所看到的基本部分是简单的和小的。 当您添加其他选项时,它会增长,例如多个承诺parsing:

 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规范。

希望我已经清楚了。

编辑:

正如评论中所述,我在这个版本中增加了两件事情:

  • 不pipe它有什么样的地位,都可以打电话给承诺。
  • 连锁承诺的可能性。

为了能够在解决时调用promise,需要将状态添加到promise中,然后调用then时检查该状态。 如果状态已解决或拒绝,只需使用其数据或错误执行callback。

为了能够链接承诺,您需要为每个呼叫产生一个新的延迟,当承诺被解决/拒绝时,解决/拒绝新的承诺和callback的结果。 所以,当承诺完成后,如果callback函数返回一个新的promise,它将绑定到由then()返回的promise。 如果不是的话,这个承诺就会被callback的结果所解决。

这是承诺:

 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在实现方面是一个非常复杂的承诺库,因为它旨在支持stream水线和RPCtypes的场景。 我在这里有我自己的骨架实现Promise / A +规范。

原则上这很简单。 在承诺解决/解决之前,通过将它们推入数组中,保留任何callback或errback的logging。 当承诺得到解决时,您打电话给相应的callback或者错误,logging承诺的结果(以及是否被完成或拒绝)。 解决后,您只需调用callback或errbacks与存储的结果。

这给了你近似的done的语义。 要构build, then你只需要返回一个新的承诺,解决了调用callbacks / errbacks的结果。

如果您对全面支持RPC和像Q这样的stream水线开发的完整实现背后的理由感兴趣,那么您可以阅读kriskowal的推理。 这是一个非常好的gradle方法,如果你正在考虑实施承诺,我不能推荐得这么好。 即使你只是使用承诺库,也可能值得一读。

正如“福布斯”在他的回答中提到的那样,我logging了许多devise决策,包括像Q这样的图书馆, https://github.com/kriskowal/q/tree/v1/design 。 可以这么说,有一个承诺图书馆的水平,许多图书馆在各级停止。

在Promise / A +规范捕获的第一级,承诺是最终结果的代理,适合pipe理“本地asynchronous” 。 也就是说,适合确保按照正确的顺序进行工作,并确保无论是否已经解决,或将来都会发生,听取操作结果的简单直接。 这也使得一方或多方可以简单地订阅最终结果。

Q,正如我所实施的那样,提供了最终,远程或最终+远程结果的承诺。 为此,它的devise是相反的,对承诺延迟承诺,履行承诺,拒绝承诺和对远程对象的承诺(最后在Q-Connection中实现)有不同的实现。 他们都共享相同的接口,通过发送和接收诸如“那么”(对于Promise / A +来说足够)的消息而工作,而且还“得到”和“调用”。 所以,Q是关于“分布式asynchronous” ,并存在于另一层。

然而,Q实际上是从更高一层下来的,承诺用于pipe理像你,商人,银行,Facebook,政府这样的相互可疑的各方之间的分布式asynchronous – 而不是敌人,甚至可能是朋友,但有时会与冲突利益。 我所实施的Q被devise成API兼容于强化的安全承诺(这是分离promiseresolve的原因),希望它能引入人们的承诺,训练他们使用这个API,并允许他们他们的代码,如果他们需要使用未来安全混搭的承诺。

当然,当你向上移动时,通常是以速度进行折衷。 所以,promise也可以被devise为共存。 这就是“可进入概念进入的地方。 每层的Promise库都可以被devise为消费其他任何层次的承诺,所以多个实现可以共存,用户只能购买他们需要的东西。

所有这一切说,没有理由难以阅读。 Domenic和我正在开发一个更为模块化,易于使用的Q版本,其中一些分散的依赖关系和解决scheme已经转移到其他模块和包中。 值得庆幸的是, “福布斯” ,“ 克罗克福德 ”等人填补了教育空白,制作了更简单的图书馆。

首先确保你了解承诺是如何工作的。 看看CommonJs Promises提案和Promise / A +规范 。

有两个基本概念可以用几个简单的语句来实现:

  • 一个承诺是asynchronous获得解决的结果。 添加callback是一个透明的操作 – 与promise是否已经被parsing无关,一旦可用,结果将被调用。

     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; } 
  • 承诺有一个允许链接他们的方法。 我接受一个callback并返回一个新的Promise,它将在第一个promise的结果被调用后得到解决。 如果callback返回一个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; }; 

进一步的概念将保持一个单独的错误状态(带有额外的callback),并捕获处理程序中的exception,或者保证callback的asynchronous性。 一旦你添加了这些,你就有了一个function齐全的Promise实现。

这是错误的事情写出来的。 它不幸的是相当重复; 你可以通过使用额外的closures来做得更好,但是这真的很难理解。

 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。

转换function – 状态转换的网守

网守function; 确保满足所有所需条件时发生状态转换。

如果条件满足,则该函数更新承诺的状态和值。 然后触发进程函数进行进一步处理。

过程函数根据转换执行正确的操作(例如待执行),稍后进行解释。

 function transition (state, value) { if (this.state === state || this.state !== validStates.PENDING || !isValidState(state)) { return; } this.value = value; this.state = state; this.process(); } 

Then函数

then函数接受两个可选参数(onFulfill和onReject处理程序),并且必须返回一个新的promise。 两大要求:

  1. 基本的承诺(被调用的承诺)需要使用传入的处理程序创build一个新的承诺; 该基地还存储一个内部引用这个创build的承诺,所以它可以被调用一旦基本的承诺被履行/拒绝。

  2. 如果基础承诺得到解决(即履行或拒绝),则应立即调用相应的处理程序。 Adehun.js通过调用then函数中的进程来处理这个场景。

 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; }` 

处理function – 处理转换

这是在状态转换之后或调用then函数时调用的。 因此它需要检查未决的promise,因为它可能是从then函数调用的。

进程对所有内部存储的承诺(即通过then函数附加到基本承诺的承诺)运行Promise Resolution过程,并执行以下Promise / A +要求:

  1. 使用Utils.runAsync helperasynchronous调用处理程序(setTimeout(setImmediate也可以))。

  2. 为onSuccess和onReject处理程序创build后备处理程序(如果缺失)。

  3. 根据承诺状态select正确的处理函数,例如履行或拒绝。

  4. 将处理程序应用于基本承诺的价值。 该操作的值被传递给Resolve函数以完成承诺处理周期。

  5. 如果发生错误,则附加的承诺立即被拒绝。

    函数的过程(){var那= this,fulfillFallBack =函数(值){返回值; },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); } }); 

    }

解决function – 解决承诺

这可能是承诺实施中最重要的部分,因为它处理承诺解决scheme。 它接受两个参数 – 承诺和分辨率值。

虽然有很多检查各种可能的分辨率值, 有趣的解决scheme是两个 – 涉及一个承诺的传入和一个可接受的(具有一个值的对象)。

  1. 通过承诺的价值

如果parsing值是另一个承诺,那么承诺必须采用这个parsing值的状态。 由于这个parsing值可以被挂起或解决,所以最简单的方法是将一个新的处理器附加到parsing值,并处理其中的原始承诺。 每当它结束时,原来的承诺将被解决或拒绝。

  1. 传递一个可观的价值

这里的问题是,那么thenable值的then函数只能被调用一次(对函数式编程的一次封装很好用)。 同样,如果检索then函数抛出一个exception,承诺将立即被拒绝。

像以前一样,then函数被调用最终parsing或拒绝承诺的函数,但是这里的差别是在第一次调用时设置的被调用标志,并且后续调用不是ops。

 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); } } 

承诺构造函数

这就是把它放在一起的那个。 完成和拒绝function是通过非操作function来parsing和拒绝的语法糖。

 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); }); } }; 

我希望这可以帮助我们更好地了解承诺的工作方式。