2 * Copyright (C) 2013 Google Inc. All rights reserved.
11 // This design for a DOM-compatible Future (nee, "Promise") type aims to be
12 // usable in the majority of the web's single-response asynchronous APIs,
13 // either directly or through subclass (e.g., to add progress notification).
15 // It also aims to enable end-users (non-spec authors) to build systems
16 // using these types directly without appealing to libraries or DOM magic
17 // for creating/fulfilling Futures.
19 // We identify the following features as required:
21 // * Chaining of .then() calls
22 // * Subclassing by DOM API designers (see ProgressFuture.idl, e.g.)
23 // * Eventual import or subsetting by TC39 for adding Futures to the
28 // Unifying or describing the internal behavior of events is a non-goal.
29 // Futures are a single-request/single-repose formalism and may capture
30 // values from past resposes. Events, on the other hand, describe a series
31 // of future events. They cover separate use-cases and we do not seek to
34 // Compatibility with existing promises/futures/deferreds libraries or
35 // terminology is a non-goal. Where adopting existing terminology improves
36 // the usability of this design, we will. Otherwise, appeals to
37 // compatibility with published libraries, specifications, and practice are
38 // not compelling. So far, this is compatible with the
39 // Promises/A+ spec, although largely by accident and in part because it
40 // leaves core compatibility points (such as defining what it means to be a
41 // testable "Promise" type in return handling) undefined:
43 // https://github.com/promises-aplus/promises-spec
47 // DOM Futures represent the completion of a single operation, past or
48 // future, an error handling for that operation.
50 // Futures take an initialization function as their only parameter. This
51 // function is called back synchronously (by the time construction finishes)
52 // with references to the the resolve and reject functions that can later be
53 // used to resolve the Future.
55 // function doAsyncWork() {
56 // return new Future(function(r) {
57 // setTimeout(function() { // ...time passes
59 // r.resolve("success");
64 // // Callers of the function can be notified of resolution easily:
65 // doAsyncWork().then(console.log.bind(console));
67 // Futures provide a way for others to be notified when the resolution
68 // occurs without handing them the ability to resolve the future itself.
69 // This is accomplished either by providing callbacks to the ".then()"
74 // the delivery of any resolution or error must be "apparently
75 // asynchronous", specifically they must be delayed at least to the "end of
76 // microtask" as defined for the delivery of mutation observer and
77 // object.observe() notifications. delivery at any point beyond that, e.g.
78 // in the next turn or microtask, is allowed, but all implementations must
79 // demonstrate the following behavior:
82 // var f = new Future(function(c) { callbacks = c; });
83 // assertTrue(f.state == "pending");
84 // callbacks.resolve(null);
85 // assertTrue(f.state == "pending");
88 // callbacks.resolve(null);
92 // // Catch the AlreadyResolved error thrown by the second resolve()
93 // assertTrue(e instanceof AlreadyResolved);
98 // Chaining is a requirement, meaning that it must be possible to call
99 // ".then()" and receive a sensible result from which you can ".then()"
102 // Futures return a new automatically-generated Future from each
103 // "then()" call. The then()-generated Futures are resolved by the
104 // callbacks passed to each "then()" invocation. These functions are called
105 // based on the resolution and their return values are passed onward to the
106 // next auto-generated Future:
108 // // Example invocation
109 // var doItAsync = function() {
111 // var f = new Future(function(c) { callbacks = c; });
116 // .then(accept, reject)
117 // .then(acceptIntermediate)
118 // .done(acceptFinal);
120 // Chaining fast-forwards un-handled values to the next item in the chain
121 // which registers a handler for that particular disposition (accept &
122 // reject). As in RSVP.js, the "onerror" function passed to done() is called
123 // here, logging the exception:
126 // .then(function(){ throw new Error("spurious!"); })
127 // .then(accept) // "accept" is not called, and we fast-forward
128 // .done(null, console.error.bind(console));
130 // If the second call were to handle "error", the final callback would not
131 // receive an "error" unless it also rejected its Future. e.g.:
134 // .then(function(){ throw new Error("spurious!"); })
135 // .then(accept, console.error.bind(console)) // logs the error
136 // .done(null, console.error.bind(console)); // does nothing
138 // Return Value Sniffing, value/error Chaining, and Forwarding:
140 // Like many other Future systems, the callbacks for accept & error
141 // are used to resolve the auto-generated Future returned from a call to
142 // then(). Below is the basic logic for then() callback return handling.
145 // * If the return is a Future, merge the generated Future's behavior
147 // * If the callback throws, reject() the generated future with the thrown
148 // value, even if the value is not an Error.
149 // * If the return value is any other kind, use it as the "value" for
150 // resolving the generated Future.
152 // TODO(slightlyoff): updated example logic
154 // Similar logic is in place when using the resolve() method to provide a
155 // value to resolve the Future with; if a value passed to resolve() is a
156 // Future, the value of the "local" future assumes the value of the "far"
157 // future. If it is any other value, it will be passed to accept(). The
158 // behavior of accept is to move the Future to the "accepted" state and
159 // set .value to whatever value is passed (regardless of type).
161 // Pseudo-code for resolve():
163 // resolverInstance.resolve = function(value) {
164 // if (isThenable(value)) {
165 // value.then(this.resolve, this.reject);
168 // this.accept(value);
169 // }.bind(resolverInstance);
173 // 1) then() callbacks in the order of registration
174 // 2) then() callbacks added after initial dispatch phase ends
176 // 2 provides for a Future to have then() callbacks added after it is
177 // resolved, allowing programmers to treat a Future object as something
178 // which is safe to call .then() on at all times. For example, both calls to
179 // then() in the following example will succeed and both callbacks will
180 // receive the resolution value, albiet at different times:
183 // var f = new Future(function(c) { callbacks = c; });
184 // setTimeout(callbacks.accept.bind(callbacks, "Success!"), 1);
185 // f.then(function(value) {
186 // // Just to illuminate the timing, we make sure that our next then()
187 // // call occurs well outside the current turn.
188 // setTimeout(function() {
189 // f.then(console.log.bind(console));
193 // We observe that the second then()-added callback *DOES* log to the
194 // console, but well after the initial resolution. All conforming
195 // implementations MUST provide the ability for post-resolution then() calls
196 // to resolve this way.
198 // Cancelation and Timeouts:
200 // The passed set of callbacks includes cancel() and timeout() functions.
201 // These are syntactic sugar that generate Error objects with known name
202 // values ("Cancel" and "Timeout", respectively). Handling these values is
203 // done through the usual reject handler of a Future. Using only accept()
204 // and reject() we can re-create these sugar functions easily:
207 // var f = new Future(function(c) { callbacks = c; });
208 // // Compatible, ad-hoc versions of cancel() and timeout()
209 // var cancel = function() { callbacks.reject(new Error("Cancel")); };
210 // var timeout = function() { callbacks.reject(new Error("Timeout")); };
212 // // Register a reject handler that understands cancelation and timeout:
213 // f.then(accept, function(reason) {
214 // switch(reason.name) {
216 // // handle cancelation...
226 // Calling either the ad-hoc cancel() or callbacks.cancel() will have the
229 // Errors and Exceptions:
231 // As seen in the example code above, exceptions throw by callback handlers
232 // are caught by the system. This leads to some particular debugging hazards
233 // which other systems have treated in various ways. Some have opted simply
234 // not to swallow errors. Some propagate errors in various ways (this design
235 // employs a variant of that approach).
237 // No matter what approach is pursued, integration with developer tooling is
238 // key. Developer consoles SHOULD log un-caught errors from future chains.
239 // That is to say, if future.done(onresponse); is the only handler given for
240 // a resolver which is eventually rejected, developer tools should log the
243 // Futures begin in the "pending" state and progress to other
244 // states. Once moved from the "pending" state, they may never be
245 // returned to it. The states are exclusive.
252 interface FutureResolver {
253 // Directly resolves the Future with the passed value
254 void accept(optional any value); // can throw AlreadyResolved
256 // Indirectly resolves the Future, chaining any passed Future's resolution
257 void resolve(optional any value); // can throw AlreadyResolved
259 // Rejects the future
260 void reject(optional any error); // can throw AlreadyResolved
262 // Rejects with an error of: Error("Cancel")
263 void cancel(); // can throw AlreadyResolved
265 // Rejects with an error of: Error("Timeout")
266 void timeout(); // can throw AlreadyResolved
268 // false until (and unless) calling any of the above methods would raise an
269 // AlreadyResolved exception
270 readonly attribute boolean isResolved;
273 callback FutureCallback = void (FutureResolver resolver);
274 callback AnyCallback = any (optional any value);
276 [Constructor(FutureCallback init)]
278 attribute readonly any value;
279 attribute readonly any error;
280 attribute readonly FutureState state;
282 // Returns a Future whose fulfillment value will be the return value
283 // from whichever of the callbacks is eventually invoked.
284 Future then(optional AnyCallback accept, optional AnyCallback reject);
286 // A shorthand for not registering only an reject callback. Desugars to:
288 // Future.prototype.catch = function(reject) {
289 // return this.then(undefined, reject);
291 Future catch(optional AnyCallback reject);
293 // An end-of-chain callback. Equivalent to .then() except it does not generate
294 // a new Future and therefore ignores the return values of the callbacks.
295 void done(optional AnyCallback accept, optional AnyCallback reject);