This document is intended to explain how promises work and why this
implementation works its particular way by building a promise library incrementally and reviewing all of its major design decisions. This is intended to leave the reader at liberty to experiment with variations of this implementation that suit their own requirements, without missing any important details.
这篇文章打算解释 promise 是怎么工作的,然后通过一步一步地创建一个 promise library,展示它所有的设计思路,来解释它是如何实现它的特定的工作的。我们想让读者能够按照他们的需求去自由地去尝试这个实现方式的变化,然后又不会错过任何细节。
Suppose that you're writing a function that can't return a value immediately.
The most obvious API is to forward the eventual value to a callback as an argument instead of returning the value.
猜想你现在正在写一个不会立即返回一个value的function。
最最容易想到的API 就是把最终的值作为一个参数用callback 带出来,而不是直接返回这个value。
var oneOneSecondLater = function (callback) {
setTimeout(function () {
callback(1);
}, 1000);
};
This is a very simple solution to a trival problem, but there is a lot of room for improvement.
这是一个非常简单的方式来解决这个小问题,但是它存在很多可以改进的地方。
A more general solution would provide analogous tools for both return values and thrown exceptions. There are several obvious ways to extend the callback pattern to handle exceptions. One is to provide both a callback and an errback.
一个更加普遍的方式就是提供既能返回value又能抛出异常的工具。有很多可以想到的方式去扩展这个callback 来处理异常。其中一个就是提供一个callback 然后再提供一个 errback。
var maybeOneOneSecondLater = function (callback, errback) {
setTimeout(function () {
if (Math.random() < .5) {
callback(1);
} else {
errback(new Error("Can't provide one."));
}
}, 1000);
};
There are other approaches, variations on providing the error as an argument to the callback, either by position or a distinguished sentinel value.
要么通过位置,要么通过区分的值,作为一个callback参数提供error的方式肯定还有很多方法和变化形式。
However, none of these approaches actually model thrown exceptions. The purpose of exceptions and try/catch blocks is to postpone the explicit handling of exceptions until the program has returned to a point where it makes sense to attempt to recover. There needs to be some mechanism for implicitly propagating exceptions if they are not handled.
然而,这些方法都没有实际上模拟抛出异常。异常和 try/catch 块的目的就是去延迟异常的显式处理,直到程序返回一个有意义去尝试恢复的点。这就需要一些机制来隐式传递那些没有被处理的异常。
Promises
Consider a more general approach, where instead of returning values or throwing exceptions, functions return an object that represents the eventual result of the function, either sucessful or failed. This object is a promise, both figuratively and by name, to eventually resolve. We can call a function on the promise to observe either its fulfillment or rejection. If the promise is rejected and the rejection is not explicitly observed, any derrived promises will be implicitly rejected for the same reason.
考虑一个更加通用的方法,方法返回一个代表最终结果函数的object,而不是返回value或者抛出异常,而不是成功或者失败。这个object就是一个 promise,无论是比喻还是字面上理解,最终解决。我们可以在promise上面调用一个方法来观察它的实现或者拒绝。如果这个promise被拒绝了并且这个拒绝没有被明确地观察到,任何得到的promise将会由于同一个原因被隐含地拒绝。
In this particular iteration of the design, we'll model a promise as an object with a "then" function that registers the callback.
在这个特定的迭代设计中,我们将promise搭建成为一个能够注册回调的具有then方法的object。
var maybeOneOneSecondLater = function () {
var callback;
setTimeout(function () {
callback(1);
}, 1000);
return {
then: function (_callback) {
callback = _callback;
}
};
};
maybeOneOneSecondLater().then(callback);
This design has two weaknesses:
- The last caller of the then method determines the callback that is used.
It would be more useful if every registered callback were notified of
the resolution. - If the callback is registered more than a second after the promise was
constructed, it won't be called.
这个设计有两个缺点:
- then 方法的最后一次调用决定了要调用的callback。如果所有的callback能够被通知到解决的话,那就更加有效果了。
- 如果callback 在promise建立后被注册了,那就它就不会被调用。
A more general solution would accept any number of callbacks and permit them
to be registered either before or after the timeout, or generally, the
resolution event. We accomplish this by making the promise a two-state object.
一个更加通用的解决方案就是接受所有的callback然后允许它们在任何时候被注册,或者一般的解决事件。我们通过让promise变成两个状态的object来实现。
A promise is initially unresolved and all callbacks are added to an array of
pending observers. When the promise is resolved, all of the observers are
notified. After the promise has been resolved, new callbacks are called
immediately. We distinguish the state change by whether the array of pending
callbacks still exists, and we throw them away after resolution.
promise刚开始还没resolve的时候,所有的callback被添加到一个等待中观察者的数组中。当这个promise被resolve的时候,所有的观察者被通知到。在promise被resolve之后,新的callback将会立即被调用。我们通过这个数组的pending callback是否还存在来区分状态的变化,然后我们在完成之后将它们丢弃。
var maybeOneOneSecondLater = function () {
var pending = [], value;
setTimeout(function () {
value = 1;
for (var i = 0, ii = pending.length; i < ii; i++) {
var callback = pending[i];
callback(value);
}
pending = undefined;
}, 1000);
return {
then: function (callback) {
if (pending) {
pending.push(callback);
} else {
callback(value);
}
}
};
};
This is already doing enough that it would be useful to break it into a
utility function. A deferred is an object with two parts: one for registering
observers and another for notifying observers of resolution.
(see design/q0.js)
这已经做的足够多了来变成一个有用的function。defer是一个有两部分的对象,一部分用来注册观察者,另一部分用来通知观察者去执行。
var defer = function () {
var pending = [], value;
return {
resolve: function (_value) {
value = _value;
for (var i = 0, ii = pending.length; i < ii; i++) {
var callback = pending[i];
callback(value);
}
pending = undefined;
},
then: function (callback) {
if (pending) {
pending.push(callback);
} else {
callback(value);
}
}
}
};
var oneOneSecondLater = function () {
var result = defer();
setTimeout(function () {
result.resolve(1);
}, 1000);
return result;
};
oneOneSecondLater().then(callback);
The resolve function now has a flaw: it can be called multiple times, changing
the value of the promised result. This fails to model the fact that a
function only either returns one value or throws one error. We can protect
against accidental or malicious resets by only allowing only the first call to
resolve to set the resolution.
resolve 方法现在有个瑕疵,它可以被调用很多次,从而会改变已经得到的结果。
这个就不能模拟一个函数只是返回一个值或者抛出一个异常。我们可以通过只允许第一次调用resolve 来设置结果来防止意外或者蓄意地重置。
var defer = function () {
var pending = [], value;
return {
resolve: function (_value) {
if (pending) {
value = _value;
for (var i = 0, ii = pending.length; i < ii; i++) {
var callback = pending[i];
callback(value);
}
pending = undefined;
} else {
throw new Error("A promise can only be resolved once.");
}
},
then: function (callback) {
if (pending) {
pending.push(callback);
} else {
callback(value);
}
}
}
};
You can make an argument either for throwing an error or for ignoring all
subsequent resolutions. One use-case is to give the resolver to a bunch of
workers and have a race to resolve the promise, where subsequent resolutions
would be ignored. It's also possible that you do not want the workers to know
which won. Hereafter, all examples will ignore rather than fault on multiple
resolution.
你可以写个参数用来抛出异常或者忽略随后的所有的resolution。一个例子就是给resolver给一些workers 然后去竞争地resolve 这个promise,随后的resolution就要被忽略掉。也有可能你并不想让workers去知道谁执行了。下面,所有的例子在多个resolution的时候都是去忽略而不是去报错。
At this point, defer can handle both multiple resolution and multiple
observation. (see design/q1.js)
这一点,defer 可以处理多个resolution也可以处理多个observation。
There are a few variations on this design which arise from two separate
tensions. The first tension is that it is both useful to separate or combine
the promise and resolver parts of the deferred. It is also useful to have
some way of distinguishing promises from other values.
这个设计有一些变化,产生于两个独立的张力。 第一个它能有效分离或结合promise和defer的resolve部分。 另一个用同一种方式将promise与其他value区分开来。
Separating the promise portion from the resolver allows us to code within the
principle of least authority. Giving someone a promise should give only the authority to observe the resolution and giving someone a resolver should only
give the authority to determine the resolution. One should not implicitly give the other. The test of time shows that any excess authority will inevitably be abused and will be very difficult to redact.
将promise部分与resolver分开允许我们在内部进行编码最低权威原则。 给某人一个promise应该只给有权遵守决议并给予某人解决方案授权决定resolution。 一个不应该隐含给另一个。 时间的考验表明,任何多余的权威都会不可避免地被滥用,将非常难以编辑。
The disadvantage of separation, however, is the additional burden on the
garbage collector to quickly dispose of used promise objects.
然而,分离的缺点,就是给垃圾收集器额外的负担来快速处理用过的promise 对象。
Also, there are a variety of ways to distinguish a promise from other values.
The most obvious and strongest distinction is to use prototypical inheritance.
(design/q2.js)
此外,有各种方法来区分promise和其他值。最显而易见的区分就是使用原型继承。
var Promise = function () {
};
var isPromise = function (value) {
return value instanceof Promise;
};
var defer = function () {
var pending = [], value;
var promise = new Promise();
promise.then = function (callback) {
if (pending) {
pending.push(callback);
} else {
callback(value);
}
};
return {
resolve: function (_value) {
if (pending) {
value = _value;
for (var i = 0, ii = pending.length; i < ii; i++) {
var callback = pending[i];
callback(value);
}
pending = undefined;
}
},
promise: promise
};
};
Using prototypical inheritance has the disadvantage that only one instance of
a promise library can be used in a single program. This can be difficult to
enforce, leading to dependency enforcement woes.
使用原型继承有个问题,就是在一个程序中只有promise的一个实例可以被使用。这个可能很难去执行,导致以来执行困难。
Another approach is to use duck-typing, distinguishing promises from other
values by the existence of a conventionally named method. In our case,
CommonJS/Promises/A establishes the use of "then" to distinguish its brand of
promises from other values. This has the disadvantage of failing to distinguish other objects that just happen to have a "then" method. In practice, this is not a problem, and the minor variations in "thenable" implementations in the wild are manageable.
另一种方法是使用鸭式,区分promises与其他值通过存在常规命名的方法。 在我们的例子中,CommonJS / Promises / A建立了使用“then”来区分其品牌承诺来自其他价值观。 这有缺点的不能区分刚刚发生的具有“then”方法的其他对象。 在实践,这不是一个问题,和“thenable”的微小变化在野外的实现是可管理的。
var isPromise = function (value) {
return value && typeof value.then === "function";
};
var defer = function () {
var pending = [], value;
return {
resolve: function (_value) {
if (pending) {
value = _value;
for (var i = 0, ii = pending.length; i < ii; i++) {
var callback = pending[i];
callback(value);
}
pending = undefined;
}
},
promise: {
then: function (callback) {
if (pending) {
pending.push(callback);
} else {
callback(value);
}
}
}
};
};
The next big step is making it easy to compose promises, to make new promises
using values obtained from old promises. Supposing that you have received promises for two numbers from a couple function calls, we would like to be able to create a promise for their sum. Consider how this is achieved with callbacks.
下一个大步骤是让它很容易撰写promise,使用从旧promise获得的值来做出新的promise。 假设你已经收到来自两个函数调用的两个数字的promise,我们希望能够为它们的总和创建一个promise。 考虑如何通过回调实现这一点。
var twoOneSecondLater = function (callback) {
var a, b;
var consider = function () {
if (a === undefined || b === undefined)
return;
callback(a + b);
};
oneOneSecondLater(function (_a) {
a = _a;
consider();
});
oneOneSecondLater(function (_b) {
b = _b;
consider();
});
};
twoOneSecondLater(function (c) {
// c === 2
});
This approach is fragile for a number of reasons, particularly that there needs to be code to explicitly notice, in this case by a sentinel value, whether a callback has been called. One must also take care to account for cases where callbacks are issued before the end of the event loop turn: the consider
function must appear before it is used.
这个方法是比较脆弱的,原因有很多,特别是需要代码明确地注意,在这个情况下,通过一个标记值,是否已经调用了callback。还必须注意在时间循环之前发出回调的情况:consider方法必须出现在它使用之前。
In a few more steps, we will be able to accomplish this using promises in less code and handling error propagation implicitly.
在更多的步骤中,我们将能够使用promise在更少的代码和隐式处理错误传播。
var a = oneOneSecondLater();
var b = oneOneSecondLater();
var c = a.then(function (a) {
return b.then(function (b) {
return a + b;
});
});
For this to work, several things have to fall into place:
- The "then" method must return a promise.
- The returned promise must be eventually resolved with the
return value of the callback. - The return value of the callback must be either a fulfilled
value or a promise.
为了这个工作,几个事情必须到位:
- then 方法必须返回一个promise。
- 返回的promise必须最终用callback返回的值resolved。
- callback返回的值不能是fulfilled的值或者是一个promise。
Converting values into promises that have already been fulfilled
is straightforward. This is a promise that immediately informs
any observers that the value has already been fulfilled.
将值转换为已经fulfill的promise是直接的。 这是一个立即通知所有observers的promise,这个值已经被fulfill了。
var ref = function (value) {
return {
then: function (callback) {
callback(value);
}
};
};
This method can be altered to coerce the argument into a promise
regardless of whether it is a value or a promise already.
这个方法可以改变以强制参数变成一个promise,不管它已经是一个值还是一个promise。
var ref = function (value) {
if (value && typeof value.then === "function")
return value;
return {
then: function (callback) {
callback(value);
}
};
};
Now, we need to start altering our "then" methods so that they return promises for the return value of their given callback. The "ref" case is simple. We'll coerce the return value of the callback to a promise and return that immediately.
现在,我们需要开始改变我们的“then”方法,使它们返回的 promise为它们给定callback的返回值。“ref”情况很简单。 我们将强制将callback的返回值变成promise并立即返回。
var ref = function (value) {
if (value && typeof value.then === "function")
return value;
return {
then: function (callback) {
return ref(callback(value));
}
};
};
This is more complicated for the deferred since the callback will be called in a future turn. In this case, we recur on "defer" and wrap the callback. The value returned by the callback will resolve the promise returned by "then".
这对于在未来从回调来的的延迟更复杂。 在这种情况下,我们再次“defer”并包装回调。 回调返回的值将会resolve “then”返回的promise。
Furthermore, the "resolve" method needs to handle the case where the resolution is itself a promise to resolve later. This is accomplished by changing the resolution value to a promise. That is, it implements a "then" method, and can either be a promise returned by "defer" or a promise returned by "ref". If it's a "ref" promise, the behavior is identical to before: the callback is called immediately by "then(callback)". If it's a "defer" promise, the callback is passed forward to the next promise by calling "then(callback)". Thus, your callback is now observing a new promise for a more fully resolved value. Callbacks can be forwarded many times, making "progress" toward an eventual resolution with each forwarding.
此外,“resolve”方法需要处理resolution本身是以后解决的promise的情况。 这是通过将resolution值更改为promise来实现的。 也就是说,它实现了一个“then”方法,可以是由“defer”返回的promise或由“ref”返回的promise。 如果它是一个“ref”promise,其行为与之前相同:回调被“then(callback)”立即调用。 如果它是一个“defer”的promise,回调被传递到下一个promise通过调用“then(callback)”。 因此,您的回调现在正在观察一个新的承诺一个更完全resolved的值。 回调可以被转发多次,使得“progress”朝向每次转发的最终resolution。
var defer = function () {
var pending = [], value;
return {
resolve: function (_value) {
if (pending) {
value = ref(_value); // values wrapped in a promise
for (var i = 0, ii = pending.length; i < ii; i++) {
var callback = pending[i];
value.then(callback); // then called instead
}
pending = undefined;
}
},
promise: {
then: function (_callback) {
var result = defer();
// callback is wrapped so that its return
// value is captured and used to resolve the promise
// that "then" returns
var callback = function (value) {
result.resolve(_callback(value));
};
if (pending) {
pending.push(callback);
} else {
value.then(callback);
}
return result.promise;
}
}
};
};
The implementation at this point uses "thenable" promises and separates the "promise" and "resolve" components of a "deferred".
(see design/q4.js)
在这个时候,实现方法使用了 thenable promises 和一个defered 分离了promise 和 resolve。
Error Propagation
To achieve error propagation, we need to reintroduce errbacks. We use a new type of promise, analogous to a "ref" promise, that instead of informing a callback of the promise's fulfillment, it will inform the errback of its rejection and the reason why.
为了实现错误传播,我们需要重新引入errbacks。 我们使用一种新的promise类型,类似于一个“ref”promise,它不是通知promise的回调的promise的实现,它会通知errback它的拒绝和原因。
var reject = function (reason) {
return {
then: function (callback, errback) {
return ref(errback(reason));
}
};
};
The simplest way to see this in action is to observe the resolution of
an immediate rejection.
看到这个在action的最简单的方法是观察的resolution立即拒绝。
reject("Meh.").then(function (value) {
// we never get here
}, function (reason) {
// reason === "Meh."
});
We can now revise our original errback use-case to use the promise
API.
我们现在可以修改我们的原始反馈用例来使用promise API。
var maybeOneOneSecondLater = function (callback, errback) {
var result = defer();
setTimeout(function () {
if (Math.random() < .5) {
result.resolve(1);
} else {
result.resolve(reject("Can't provide one."));
}
}, 1000);
return result.promise;
};
To make this example work, the defer system needs new plumbing so that it can forward both the callback and errback components. So, the array of pending callbacks will be replaced with an array of arguments for "then" calls.
为了使此示例工作,延迟系统需要新的管道,以便它可以转发回调和errback组件。 因此,待处理回调的数组将被“then”调用的参数数组替换。
var defer = function () {
var pending = [], value;
return {
resolve: function (_value) {
if (pending) {
value = ref(_value);
for (var i = 0, ii = pending.length; i < ii; i++) {
// apply the pending arguments to "then"
value.then.apply(value, pending[i]);
}
pending = undefined;
}
},
promise: {
then: function (_callback, _errback) {
var result = defer();
var callback = function (value) {
result.resolve(_callback(value));
};
var errback = function (reason) {
result.resolve(_errback(reason));
};
if (pending) {
pending.push([callback, errback]);
} else {
value.then(callback, errback);
}
return result.promise;
}
}
};
};
There is, however, a subtle problem with this version of "defer". It mandates that an errback must be provided on all "then" calls, or an exception will be thrown when trying to call a non-existant function. The simplest solution to this problem is to provide a default errback that forwards the rejection. It is also reasonable for the callback to be omitted if you're only interested in observing rejections, so we provide a default callback that forwards the fulfilled value.
但是,这个版本的“defer”有一个微妙的问题。 它要求必须在所有“then”调用上提供errback,或者在尝试调用不存在的函数时抛出异常。 此问题的最简单的解决方案是提供转发拒绝的默认errback。 如果你只关注观察拒绝,那么回调被省略也是合理的,所以我们提供一个默认的callback来转发fulfill的值。
var defer = function () {
var pending = [], value;
return {
resolve: function (_value) {
if (pending) {
value = ref(_value);
for (var i = 0, ii = pending.length; i < ii; i++) {
value.then.apply(value, pending[i]);
}
pending = undefined;
}
},
promise: {
then: function (_callback, _errback) {
var result = defer();
// provide default callbacks and errbacks
_callback = _callback || function (value) {
// by default, forward fulfillment
return value;
};
_errback = _errback || function (reason) {
// by default, forward rejection
return reject(reason);
};
var callback = function (value) {
result.resolve(_callback(value));
};
var errback = function (reason) {
result.resolve(_errback(reason));
};
if (pending) {
pending.push([callback, errback]);
} else {
value.then(callback, errback);
}
return result.promise;
}
}
};
};
At this point, we've achieved composition and implicit error propagation. We can now very easily create promises from other promises either in serial or in parallel (see design/q6.js). This example creates a promise for the eventual sum of promised values.
在这一点上,我们已经实现了组合和隐式错误传播。 我们现在可以很容易地从串行或并行的其他promise创建promise(见design / q6.js)。 此示例为promise的值的最终和创建一个promise。
promises.reduce(function (accumulating, promise) {
return accumulating.then(function (accumulated) {
return promise.then(function (value) {
return accumulated + value;
});
});
}, ref(0)) // start with a promise for zero, so we can call then on it
// just like any of the combined promises
.then(function (sum) {
// the sum is here
});
Safety and Invariants
Another incremental improvement is to make sure that callbacks and errbacks are called in future turns of the event loop, in the same order that they were registered. This greatly reduces the number of control-flow hazards inherent to asynchronous programming. Consider a brief and contrived example:
另一个增量改进是确保在事件循环的未来轮次中调用回调和errback,顺序与它们注册的顺序相同。 这大大减少了异步编程固有的控制流危险的数量。 考虑一个简单的和设计的例子:
var blah = function () {
var result = foob().then(function () {
return barf();
});
var barf = function () {
return 10;
};
return result;
};
This function will either throw an exception or return a promise that will quickly be fulfilled with the value of 10. It depends on whether foob() resolves in the same turn of the event loop (issuing its callback on the same stack immediately) or in a future turn. If the callback is delayed to a future turn, it will allways succeed.
(see design/q7.js)
这个函数将抛出一个异常或者返回一个promise,它很快就会被赋值为10.它取决于foob()是否在同一个事件循环中解决(立即在同一堆栈上发出它的回调),或者在未来的回合。 如果回调被延迟到未来的回合,它将会成功。
(见设计/ q7.js)
var enqueue = function (callback) {
//process.nextTick(callback); // NodeJS
setTimeout(callback, 1); // Naïve browser solution
};
var defer = function () {
var pending = [], value;
return {
resolve: function (_value) {
if (pending) {
value = ref(_value);
for (var i = 0, ii = pending.length; i < ii; i++) {
// XXX
enqueue(function () {
value.then.apply(value, pending[i]);
});
}
pending = undefined;
}
},
promise: {
then: function (_callback, _errback) {
var result = defer();
_callback = _callback || function (value) {
return value;
};
_errback = _errback || function (reason) {
return reject(reason);
};
var callback = function (value) {
result.resolve(_callback(value));
};
var errback = function (reason) {
result.resolve(_errback(reason));
};
if (pending) {
pending.push([callback, errback]);
} else {
// XXX
enqueue(function () {
value.then(callback, errback);
});
}
return result.promise;
}
}
};
};
var ref = function (value) {
if (value && value.then)
return value;
return {
then: function (callback) {
var result = defer();
// XXX
enqueue(function () {
result.resolve(callback(value));
});
return result.promise;
}
};
};
var reject = function (reason) {
return {
then: function (callback, errback) {
var result = defer();
// XXX
enqueue(function () {
result.resolve(errback(reason));
});
return result.promise;
}
};
};
There remains one safty issue, though. Given that any object that implements "then" is treated as a promise, anyone who calls "then" directly is at risk of surprise.
但仍有一个安全问题。 假设实现“then”的对象被视为promise,那么任何直接调用“then”的人都会有意外的风险。
- The callback or errback might get called in the same turn
- The callback and errback might both be called
- The callback or errback might be called more than once
- callback 和 errback 可能在同一个回合被调用。
- callback 和 errback 可能两个都被调用。
- callback 和 errback 可能被调用不止一次。
A "when" method wraps a promise and prevents these surprises.
when 方法包装了promise 然后可以阻止这些意外。
We can also take the opportunity to wrap the callback and errback
so that any exceptions thrown get transformed into rejections.
我们还是可以利用机会去包装callback 和 errback,以至于任何抛出的异常都会被转换到rejection里面去。
var when = function (value, _callback, _errback) {
var result = defer();
var done;
_callback = _callback || function (value) {
return value;
};
_errback = _errback || function (reason) {
return reject(reason);
};
var callback = function (value) {
try {
return _callback(value);
} catch (reason) {
return reject(reason);
}
};
var errback = function (reason) {
try {
return _errback(reason);
} catch (reason) {
return reject(reason);
}
};
enqueue(function () {
ref(value).then(function (value) {
if (done)
return;
done = true;
result.resolve(ref(value).then(callback, errback));
}, function (reason) {
if (done)
return;
done = true;
result.resolve(errback(reason));
});
});
return result.promise;
};
At this point, we have the means to protect ourselves against several
surprises including unnecessary non-deterministic control-flow in the course of an event and broken callback and errback control-flow invariants.
(see design/q7.js)
在这一点上,我们有办法保护自己免受几个意外,包括在事件过程中不必要的非确定性控制流和破坏的回调和错误控制流不变量。
Message Passing
If we take a step back, promises have become objects that receive "then" messages. Deferred promises forward those messages to their resolution promise. Fulfilled promises respond to then messages by calling the callback with the fulfilled value. Rejected promises respond to then messages by calling the errback with the rejection reason.
如果我们退后一步,promise已经成为接收“then”消息的对象。 defer的promise将这些信息转发给他们的resolution promise。 fulfill的promise通过调用具有满足值的回调来响应然后的消息。 reject的promise通过调用具有拒绝原因的errback响应然后消息。
We can generalize promises to be objects that receive arbitrary messages, including "then/when" messages. This is useful if there is a lot of latency preventing the immediate observation of a promise's resolution, as in a promise that is in another process or worker or another computer on a network.
我们可以将promise概括为接收任意消息的对象,包括“then / when”消息。 如果有大量延迟阻止立即观察承诺的解决方案,如在另一个进程或工作线程或网络上的另一台计算机中的promise,这是非常有用的。
If we have to wait for a message to make a full round-trip across a network to get a value, the round-trips can add up a lot and much time will be wasted. This ammounts to "chatty" network protocol problems, which are the downfall of SOAP and RPC in general.
如果我们必须等待一个消息,通过网络做一个完整的往返,以获得一个价值,往返行程可以累加很多,很多时间会浪费。 这就是“chatty”网络协议问题,这是SOAP和RPC的失败。
However, if we can send a message to a distant promise before it resolves, the remote promise can send responses in rapid succession. Consider the case where an object is housed on a remote server and cannot itself be sent across the network; it has some internal state and capabilities that cannot be serialized, like access to a database. Suppose we obtain a promise for this object and can now send messages. These messages would likely mostly
comprise method calls like "query", which would in turn send promises back.
然而,如果我们可以在它解析之前向远程promise发送消息,则远程promise可以快速连续地发送响应。 考虑其中对象容纳在远程服务器上并且本身不能通过网络发送的情况; 它有一些内部状态和功能,无法序列化,如访问数据库。 假设我们获得了这个对象的promise,现在可以发送消息。 这些消息可能主要包括诸如“查询”的方法调用,其将反过来发送promise。
We must found a new family of promises based on a new method that sends arbitrary messages to a promise. "promiseSend" is defined by
CommonJS/Promises/D. Sending a "when" message is equivalent to calling the "then" method.
我们必须找到一个新的承诺的家庭基于一个新的方法,发送任意消息的承诺。 “promiseSend”由定义 CommonJS / Promises / D。 发送“when”消息等同于调用“then”方法。
promise.then(callback, errback);
promise.promiseSend("when", callback, errback);
We must revisit all of our methods, building them on "promiseSend" instead of "then". However, we do not abandon "then" entirely; we still produce and consume "thenable" promises, routing their message through "promiseSend" internally.
我们必须重新访问所有的方法,在“promiseSend”而不是“then”上构建它们。 然而,我们不会完全放弃“then” 我们仍然产生和消费“thenable”promise,通过“promiseSend”在内部路由他们的消息。
function Promise() {}
Promise.prototype.then = function (callback, errback) {
return when(this, callback, errback);
};
If a promise does not recognize a message type (an "operator" like "when"),it must return a promise that will be eventually rejected.
如果promise不能识别消息类型(一个“运算符”,例如“when”),它必须返回一个最终被拒绝的promise。
Being able to receive arbitrary messages means that we can also implement new types of promise that serves as a proxy for a remote promise, simply forwarding all messages to the remote promise and forwarding all of its responses back to promises in the local worker.
能够接收任意消息意味着我们还可以实现用作远程promise的代理的新类型的promise,简单地将所有消息转发到远程promise并将其所有响应转发回本地工作者中的promise。
Between the use-case for proxies and rejecting unrecognized messages, it is useful to create a promise abstraction that routes recognized messages to a handler object, and unrecognized messages to a fallback method.
在代理的用例和拒绝不可识别的消息之间,创建一个promise抽象,将已识别的消息路由到处理程序对象,并将无法识别的消息路由到回退方法是有用的。
var makePromise = function (handler, fallback) {
var promise = new Promise();
handler = handler || {};
fallback = fallback || function (op) {
return reject("Can't " + op);
};
promise.promiseSend = function (op, callback) {
var args = Array.prototype.slice.call(arguments, 2);
var result;
callback = callback || function (value) {return value};
if (handler[op]) {
result = handler[op].apply(handler, args);
} else {
result = fallback.apply(handler, [op].concat(args));
}
return callback(result);
};
return promise;
};
Each of the handler methods and the fallback method are all expected to return a value which will be forwarded to the callback. The handlers do not receive their own name, but the fallback does receive the operator name so it can route it. Otherwise, arguments are passed through.
每个处理程序方法和回退方法都希望返回一个值,该值将被转发到回调。 处理程序不接收自己的名称,但是后备接收操作员名称,以便它可以路由它。 否则,传递参数。
For the "ref" method, we still only coerce values that are not already
promises. We also coerce "thenables" into "promiseSend" promises.
We provide methods for basic interaction with a fulfilled value, including property manipulation and method calls.
对于“ref”方法,我们仍然只强制值已经不是promise。 我们也强迫“thenables”成“promiseSend”承诺。我们提供了与满足值的基本交互的方法,包括属性操作和方法调用。
var ref = function (object) {
if (object && typeof object.promiseSend !== "undefined") {
return object;
}
if (object && typeof object.then !== "undefined") {
return makePromise({
when: function () {
var result = defer();
object.then(result.resolve, result.reject);
return result;
}
}, function fallback(op) {
return Q.when(object, function (object) {
return Q.ref(object).promiseSend.apply(object, arguments);
});
});
}
return makePromise({
when: function () {
return object;
},
get: function (name) {
return object[name];
},
put: function (name, value) {
object[name] = value;
},
del: function (name) {
delete object[name];
}
});
};
Rejected promises simply forward their rejection to any message.
rejected promise 只是转发他们rejection任何消息。
var reject = function (reason) {
var forward = function (reason) {
return reject(reason);
};
return makePromise({
when: function (errback) {
errback = errback || forward;
return errback(reason);
}
}, forward);
};
Defer sustains very little damage. Instead of having an array of arguments to forward to "then", we have an array of arguments to forward to "promiseSend". "makePromise" and "when" absorb the responsibility for handling the callback and errback argument defaults and wrappers.
defer受到的伤害很小。 而不是有一个参数数组转发到“then”,我们有一个参数数组转发到“promiseSend”。 “makePromise”和“when”吸收处理回调和errback参数默认值和包装器的责任。
var defer = function () {
var pending = [], value;
return {
resolve: function (_value) {
if (pending) {
value = ref(_value);
for (var i = 0, ii = pending.length; i < ii; i++) {
enqueue(function () {
value.promiseSend.apply(value, pending[i]);
});
}
pending = undefined;
}
},
promise: {
promiseSend: function () {
var args = Array.prototype.slice.call(arguments);
var result = defer();
if (pending) {
pending.push(args);
} else {
enqueue(function () {
value.promiseSend.apply(value, args);
});
}
}
}
};
};
The last step is to make it syntactically convenient to send messages to promises. We create "get", "put", "post" and "del" functions that send the corresponding messages and return promises for the results. They all look very similar.
最后一步是使语法方便地向promise发送消息。 我们创建“get”,“put”,“post”和“del”函数,发送相应的消息并返回结果的promise。 他们都看起来很相似。
var get = function (object, name) {
var result = defer();
ref(object).promiseSend("get", result.resolve, name);
return result.promise;
};
get({"a": 10}, "a").then(function (ten) {
// ten === ten
});
The last improvment to get promises up to the state-of-the-art is to rename all of the callbacks to "win" and all of the errbacks to "fail". I've left this as an exercise.
获得最新技术的最后一个改进是将所有回调重命名为“win”,并将所有redback重命名为“fail”。 我把这作为一个练习。
Future
Andrew Sutherland did a great exercise in creating a variation of the Q library that supported annotations so that waterfalls of promise creation, resolution, and dependencies could be graphically depicited. Optional annotations and a debug variation of the Q library would be a logical next-step.
There remains some question about how to ideally cancel a promise. At the moment, a secondary channel would have to be used to send the abort message.
This requires further research.
CommonJS/Promises/A also supports progress notification callbacks. A variation of this library that supports implicit composition and propagation of progress information would be very awesome.
It is a common pattern that remote objects have a fixed set of methods, all of which return promises. For those cases, it is a common pattern to create a local object that proxies for the remote object by forwarding all of its method calls to the remote object using "post". The construction of such proxies could be automated. Lazy-Arrays are certainly one use-case.
简单翻译自 kriskowal/q
网友评论