1
/* Copyright (c) 2009-2010 litl, LLC
3
* Permission is hereby granted, free of charge, to any person obtaining a copy
4
* of this software and associated documentation files (the "Software"), to deal
5
* in the Software without restriction, including without limitation the rights
6
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
* copies of the Software, and to permit persons to whom the Software is
8
* furnished to do so, subject to the following conditions:
10
* The above copyright notice and this permission notice shall be included in
11
* all copies or substantial portions of the Software.
13
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
/* Promises represent a value which will be computed in the future,
24
* although it may not be available yet.
26
* An "async calls" convention is then built on promises, in
27
* async.js. It is generally possible to write code using only the
28
* Async API without explicitly using Promise. It's also possible
29
* (but often more typing) to write code with explicit Promise
30
* manipulation. One or the other may be nicer in a given instance.
32
* Convention: when an API returns a promise, that promise should be
33
* "kicked off" already (the thread or main loop source should be
34
* started). It should not be necessary to invoke get() on the promise
35
* to get the computation going.
37
* Convention: it is OK for Promise.get() to invoke the onReturn or
38
* onError synchronously (_before_ the get() returns). This can
39
* occasionally be surprising, but the alternatives are not
40
* good either and the synchronous result can be useful.
43
/** default onError handler, makes debugging missing callbacks easier.
44
* It is a bug if this handler gets called.
46
const DEFAULT_ONERROR = function(e) {
47
logError(e, "No onError handler");
50
/* This is publicly exported from async.js, but is defined here to
51
* avoid a circular dependency.
53
* The issue is that we want async.js to build on Promise, but as
54
* a nice tweak want Promise.get to be an async call as defined
57
let _asyncFunc = function(f) {
58
/* Create a task from an Asynchronous Function, providing 'this' as
59
* the first argument. */
60
f.asyncCall = function() {
61
let params = Array.slice(arguments);
64
return _asyncCall.apply(me, params);
69
/* This is publicly exported from async.js, but is defined here to
70
* avoid a circular dependency.
72
* _asyncCall is needed by _asyncFunc above)
74
let _asyncCall = function(f) {
75
let params = Array.slice(arguments, 1); // take off 'f'
76
let promise = new Promise();
77
let onReturn = function(v) { promise.putReturn(v); };
78
let onError = function(e) { promise.putError(e); };
79
params.unshift(onReturn, onError);
80
f.apply(this /* preserve 'this' */, params);
84
/** Prototype for a Promise.
86
* A promise is a value (or error) that may not be available yet.
87
* To obtain this value, we may need to return to the main loop
88
* or run a main loop recursively.
92
function Promise() { this._queue = []; }
96
return this.hasOwnProperty('_queue');
99
/** Invoke 'onReturn' on the result of this promise, when
100
* it completes. Any error will invoke 'onError' with the thrown
101
* exception. (The get method of a Promise is itself
102
* an Asynchronous Function, see async.js)
104
get : _asyncFunc(function(onReturn, onError) {
106
throw new Error("get after get (should be cached!)");
107
onError = onError || DEFAULT_ONERROR; // catch bugs
108
/* no value available yet, queue continuations. */
109
this._queue.push({ onReturn: onReturn, onError: onError });
112
/** Set a normal value of a promise (only callable once), invoking any
113
* queued onReturn continuations as necessary.
115
putReturn : function(returnValue) {
117
throw new Error("putReturn after put");
118
// mark this promise as no longer 'waiting'
119
let queue = this._queue;
121
// prevent further queuing
122
this.get = _asyncFunc(function(onReturn, onError) {
123
onReturn(returnValue);
125
// okay, now invoke queued callbacks.
126
for each (let cb in queue) {
128
cb.onReturn(returnValue);
130
logError(e, "Error in onReturn callback");
131
// but make sure all other callbacks are still invoked.
135
/** Set an error value of a promise (only callable once), invoking any
136
* queued onError continuations as necessary.
138
putError : function(errorValue) {
140
throw new Error("putError after put");
141
// mark this promise as no longer 'waiting'
142
let queue = this._queue;
144
// prevent further queuing
145
this.get = _asyncFunc(function(onReturn, onError) {
148
// okay, now invoke queued callbacks.
149
for each (let cb in queue) {
151
cb.onError(errorValue);
153
logError(e, "Error in onError callback");
154
// but make sure all other callbacks are still invoked.
159
/** Utility method: fire off the promise, don't wait for a result (but
160
* log any error which occurs).
162
fireAndForget: function() {
163
this.get(function(){}, DEFAULT_ONERROR);
166
/** Utility method: sets our return (or error) to the result of
167
* another promise. Allows easily chaining promises. If you
168
* putPromisedReturn(undefined) (or with no args) then it is
169
* equivalent to putReturn(undefined), it immediately completes
170
* the promise but with no result value.
172
putPromisedReturn : function(promiseOfReturn) {
174
if (promiseOfReturn !== undefined) {
175
promiseOfReturn.get(function(v) {
176
promise.putReturn(v);
186
toString: function() {
191
/** Create a Promise representing the future construction of an object using
192
* the given constructor (first arg) and arguments array. The value of the
193
* promise is the fully-constructed object.
195
* The constructor must be a special "async constructor" which has
196
* onComplete and onError functions as the first two args.
198
* @returns a new promise, with newly-constructed object as expected value
200
let fromConstructor = function(constructor, params) {
201
params = params || [];
203
__proto__: constructor.prototype,
204
constructor: constructor
207
let promise = new Promise();
208
// return the constructed object when the constructor completes
209
let onComplete = function() { promise.putReturn(newobj); };
210
let onError = function(e) { promise.putError(e); };
211
params.unshift(onComplete, onError);
213
constructor.apply(newobj, params);
217
/** Converts a synchronous function into a promise. This is mostly
218
* useful for testing, since the promise is never actually deferred,
219
* of course. In fact the function gets called immediately.
220
* We don't do anything with threads or the main loop here.
222
* @returns a new promise, with value set to result of invoking function
224
let fromSync = function(f, params) {
225
params = params || [];
226
let promise = new Promise();
228
let v = f.apply(this, params);
229
promise.putReturn(v);
236
/** Converts a value to an already-completed promise.
237
* This is useful when you have a synchronous result
238
* already available and want to return it through an
239
* abstract API that returns a Promise.
241
* @returns a new promise with the given value already set
243
let fromValue = function(v) {
244
let promise = new Promise();
245
promise.putReturn(v);
249
let _oneGeneratorStep = function(g, retval, isException, generatorResultPromise) {
251
/* get the next asynchronous task to execute from the generator */
252
let promise = (isException) ? g.throw(retval) : g.send(retval);
253
/* execute it, with a continuation which will send the result
254
* back to the generator (whether normal or exception) and
255
* loop (with a tail call). */
256
promise.get(function(v) {
257
_oneGeneratorStep(g, v, false, generatorResultPromise);
259
_oneGeneratorStep(g, e, true, generatorResultPromise);
262
/* before handling the exception, close the generator */
266
/* same semantics as javascript: exception thrown in
267
* finally clause overrides any other exception or
269
generatorResultPromise.putError(ee);
272
if (e === StopIteration) {
273
/* generator exited without returning a value. */
274
generatorResultPromise.putReturn(); /* done */
275
} else if ('_asyncRetval' in e) {
276
/* generator exited returning a value. */
277
generatorResultPromise.putReturn(e._asyncRetval);
279
/* generator threw an exception explicitly. */
280
generatorResultPromise.putError(e);
285
/** Converts a generator function (with "this" and array of params)
286
* into a promise that promises the error or return value of the
287
* generator function.
289
* A generator function should contain statements of the form:
292
* let promiseResult = yield promise;
293
* } catch (promiseError) {
296
* At each such yield, the fromGenerator() driver will take
297
* back control, let the promise complete, then pass back
298
* control giving the result of the promise (or throwing the error
301
* The power of this is that the generator function can perform a series
302
* of async actions, blocking on each promise in turn, while still having
303
* a nice apparent flow of control. In other words this is a great way
304
* to implement a chain of async steps, where each depends on the previous.
306
* The generator function can return a value by calling
307
* Promise.putReturnFromGenerator(), which is implemented somewhat hackily
308
* by throwing a special kind of exception. The result is that the
309
* generator ends, and you can Promise.get() the returned value from
310
* the promise that fromGenerator() gave you. If the generator just
311
* falls off the end without calling Promise.putReturnFromGenerator(), the
312
* promise will have an undefined value.
314
* @returns a new promise whose eventual value depends on the generator
317
let fromGenerator = function(g, self, params) {
318
params = params || [];
319
let generatorResultPromise = new Promise();
322
let generator = g.apply(self, params);
323
_oneGeneratorStep(generator, undefined, false, generatorResultPromise);
325
/* catch exceptions invoking g() to create generator. */
326
generatorResultPromise.putError(e);
329
return generatorResultPromise;
332
/** Calls putReturn() on the promise returned by fromGenerator(),
333
* ending that generator and completing its promise.
335
let putReturnFromGenerator = function(val) {
336
throw { _asyncRetval: val };