forked from slightlyoff/Promises
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDOMFuture.idl
309 lines (295 loc) · 12.3 KB
/
DOMFuture.idl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
*/
//
// Design Notes
// ============
//
// Goals:
//
// This design for a DOM-compatible Future (nee, "Promise") type aims to be
// usable in the majority of the web's single-response asynchronous APIs,
// either directly or through subclass (e.g., to add progress notification).
//
// It also aims to enable end-users (non-spec authors) to build systems
// using these types directly without appealing to libraries or DOM magic
// for creating/fulfilling Futures.
//
// We identify the following features as required:
//
// * Chaining of .then() calls
// * Subclassing by DOM API designers (see ProgressFuture.idl, e.g.)
// * Eventual import or subsetting by TC39 for adding Futures to the
// language directly.
//
// Non-Goals:
//
// Unifying or describing the internal behavior of events is a non-goal.
// Futures are a single-request/single-repose formalism and may capture
// values from past resposes. Events, on the other hand, describe a series
// of future events. They cover separate use-cases and we do not seek to
// join them here.
//
// Compatibility with existing promises/futures/deferreds libraries or
// terminology is a non-goal. Where adopting existing terminology improves
// the usability of this design, we will. Otherwise, appeals to
// compatibility with published libraries, specifications, and practice are
// not compelling. So far, this is compatible with the
// Promises/A+ spec, although largely by accident and in part because it
// leaves core compatibility points (such as defining what it means to be a
// testable "Promise" type in return handling) undefined:
//
// https://github.com/promises-aplus/promises-spec
//
// Basic API:
//
// DOM Futures represent the completion of a single operation, past or
// future, an error handling for that operation.
//
// Futures take an initialization function as their only parameter. This
// function is called back synchronously (by the time construction finishes)
// with references to the the resolve and reject functions that can later be
// used to resolve the Future.
//
// function doAsyncWork() {
// return new Future(function(r) {
// setTimeout(function() { // ...time passes
// // Do work here
// r.resolve("success");
// }, 100);
// });
// }
//
// // Callers of the function can be notified of resolution easily:
// doAsyncWork().then(console.log.bind(console));
//
// Futures provide a way for others to be notified when the resolution
// occurs without handing them the ability to resolve the future itself.
// This is accomplished either by providing callbacks to the ".then()"
// method.
//
// Processing model:
//
// the delivery of any resolution or error must be "apparently
// asynchronous", specifically they must be delayed at least to the "end of
// microtask" as defined for the delivery of mutation observer and
// object.observe() notifications. delivery at any point beyond that, e.g.
// in the next turn or microtask, is allowed, but all implementations must
// demonstrate the following behavior:
//
// var callbacks;
// var f = new Future(function(c) { callbacks = c; });
// assertTrue(f.state == "pending");
// callbacks.resolve(null);
// assertTrue(f.state == "pending");
//
// try {
// callbacks.resolve(null);
// // Not reached
// assertTrue(false);
// } catch(e) {
// // Catch the AlreadyResolved error thrown by the second resolve()
// assertTrue(e instanceof AlreadyResolved);
// }
//
// Chaining:
//
// Chaining is a requirement, meaning that it must be possible to call
// ".then()" and receive a sensible result from which you can ".then()"
// again.
//
// Futures return a new automatically-generated Future from each
// "then()" call. The then()-generated Futures are resolved by the
// callbacks passed to each "then()" invocation. These functions are called
// based on the resolution and their return values are passed onward to the
// next auto-generated Future:
//
// // Example invocation
// var doItAsync = function() {
// var callbacks;
// var f = new Future(function(c) { callbacks = c; });
// // ...
// return f;
// };
// doItAsync()
// .then(onaccept, onreject)
// .then(onacceptIntermediate)
// .done(onacceptFinal);
//
// Chaining fast-forwards un-handled values to the next item in the chain
// which registers a handler for that particular disposition (accept &
// reject). As in RSVP.js, the "onerror" function passed to done() is called
// here, logging the exception:
//
// doItAsync()
// .then(function(){ throw new Error("spurious!"); })
// .then(onaccept) // "onaccept" is not called, and we fast-forward
// .done(null, console.error.bind(console));
//
// If the second call were to handle "error", the final callback would not
// receive an "error" unless it also rejected its Future. e.g.:
//
// doItAsync()
// .then(function(){ throw new Error("spurious!"); })
// .then(onaccept, console.error.bind(console)) // logs the error
// .done(null, console.error.bind(console)); // does nothing
//
// Return Value Sniffing, value/error Chaining, and Forwarding:
//
// Like many other Future systems, the callbacks for accept & error
// are used to resolve the auto-generated Future returned from a call to
// then(). Below is the basic logic for then() callback return handling.
// Major features:
//
// * If the return is a Future, merge the generated Future's behavior
// with it.
// * If the callback throws, reject() the generated future with the thrown
// value, even if the value is not an Error.
// * If the return value is any other kind, use it as the "value" for
// resolving the generated Future.
//
// TODO(slightlyoff): updated example logic
//
// Similar logic is in place when using the resolve() method to provide a
// value to resolve the Future with; if a value passed to resolve() is a
// Future, the value of the "local" future assumes the value of the "far"
// future. If it is any other value, it will be passed to accept(). The
// behavior of accept is to move the Future to the "accepted" state and
// set .value to whatever value is passed (regardless of type).
//
// Pseudo-code for resolve():
//
// resolverInstance.resolve = function(value) {
// if (isThenable(value)) {
// value.then(this.resolve, this.reject);
// return;
// }
// this.accept(value);
// }.bind(resolverInstance);
//
// Delivery order:
//
// 1) then() callbacks in the order of registration
// 2) then() callbacks added after initial dispatch phase ends
//
// 2 provides for a Future to have then() callbacks added after it is
// resolved, allowing programmers to treat a Future object as something
// which is safe to call .then() on at all times. For example, both calls to
// then() in the following example will succeed and both callbacks will
// receive the resolution value, albiet at different times:
//
// var callbacks;
// var f = new Future(function(c) { callbacks = c; });
// setTimeout(callbacks.accept.bind(callbacks, "Success!"), 1);
// f.then(function(value) {
// // Just to illuminate the timing, we make sure that our next then()
// // call occurs well outside the current turn.
// setTimeout(function() {
// f.then(console.log.bind(console));
// }, 1);
// });
//
// We observe that the second then()-added callback *DOES* log to the
// console, but well after the initial resolution. All conforming
// implementations MUST provide the ability for post-resolution then() calls
// to resolve this way.
//
// Cancelation and Timeouts:
//
// The passed set of callbacks includes cancel() and timeout() functions.
// These are syntactic sugar that generate Error objects with known name
// values ("Cancel" and "Timeout", respectively). Handling these values is
// done through the usual reject handler of a Future. Using only accept()
// and reject() we can re-create these sugar functions easily:
//
// var callbacks;
// var f = new Future(function(c) { callbacks = c; });
// // Compatible, ad-hoc versions of cancel() and timeout()
// var cancel = function() { callbacks.reject(new Error("Cancel")); };
// var timeout = function() { callbacks.reject(new Error("Timeout")); };
//
// // Register a reject handler that understands cancelation and timeout:
// f.then(onaccept, function(reason) {
// switch(reason.name) {
// case "Cancel":
// // handle cancelation...
// break;
// case "Timeout":
// // ...
// break;
// default:
// break;
// }
// });
//
// Calling either the ad-hoc cancel() or callbacks.cancel() will have the
// same effect.
//
// Errors and Exceptions:
//
// As seen in the example code above, exceptions throw by callback handlers
// are caught by the system. This leads to some particular debugging hazards
// which other systems have treated in various ways. Some have opted simply
// not to swallow errors. Some propagate errors in various ways (this design
// employs a variant of that approach).
//
// No matter what approach is pursued, integration with developer tooling is
// key. Developer consoles SHOULD log un-caught errors from future chains.
// That is to say, if future.done(onresponse); is the only handler given for
// a resolver which is eventually rejected, developer tools should log the
// unhandled error.
// Futures begin in the "pending" state and progress to other
// states. Once moved from the "pending" state, they may never be
// returned to it. The states are exclusive.
enum FutureState {
"pending",
"accepted",
"rejected"
};
interface ResolverCallbacks {
// Directly resolves the Future with the passed value
void accept(optional any value) raises(Error); // AlreadyResolved
// Indirectly resolves the Future, chaining any passed Future's resolution
void resolve(optional any value) raises(Error); // AlreadyResolved
// Rejects the future
void reject(optional any error) raises(Error); // AlreadyResolved
// Rejects with an error of: Error("Cancel")
void cancel() raises(Error); // AlreadyResolved
// Rejects with an error of: Error("Timeout")
void timeout() raises(Error); // AlreadyResolved
// false until (and unless) calling any of the above methods would raise an
// AlreadyResolved exception
attribute readonly boolean isResolved; // FIXME(slightlyoff): getter?
};
callback InitCallback = void (ResolverCallbacks callbacks);
callback AnyCallback = any (optional any value);
[Constructor(InitCallback init)]
interface Future {
attribute readonly any value;
attribute readonly any error;
attribute readonly FutureState state;
// Returns a Future whose fulfillment value will be the return value
// from whichever of the callbacks is eventually invoked.
Future then(optional AnyCallback onaccept, optional AnyCallback onreject);
// A shorthand for not registering only an onreject callback. Desugars to:
//
// Future.prototype.catch = function(onreject) {
// return this.then(undefined, onreject);
// };
Future catch(optional AnyCallback onreject);
// An end-of-chain callback. Equivalent to .then() except it does not generate
// a new Future and therefore ignores the return values of the callbacks.
void done(optional AnyCallback onaccept, optional AnyCallback onreject);
// FIXME(slightlyoff):
// we don't have an extension point in the then() scheme that mirrors the
// way we allow extension via the callbacks object in the Future
// constructor. This turns into a big problem for DOM APIs that need to be
// retrofitted to enable additional semantics and callbacks for them in a
// natrual way.
//
// Future then(required any callbacks);
// .then({ accept: function(v) { ... },
// reject: function(e) { ... },
// ...
// })
};