]>
Commit | Line | Data |
---|---|---|
6527f429 DM |
1 | /*\r |
2 | Ext.promise.Deferred adapted from:\r | |
3 | [DeftJS](https://github.com/deftjs/deftjs5)\r | |
4 | Copyright (c) 2012-2014 [DeftJS Framework Contributors](http://deftjs.org)\r | |
5 | Open source under the [MIT License](http://en.wikipedia.org/wiki/MIT_License).\r | |
6 | */\r | |
7 | \r | |
8 | /**\r | |
9 | * Promises represent a future value; i.e., a value that may not yet be available.\r | |
10 | *\r | |
11 | * Users should **not** create instances of this class directly. Instead user code should\r | |
12 | * use `new {@link Ext.Promise}()` or `new {@link Ext.Deferred}()` to create and manage\r | |
13 | * promises. If the browser supports the standard `Promise` constructor, this class will\r | |
14 | * not be used by `Ext.Promise`. This class will always be used by `Ext.Deferred` in order\r | |
15 | * to provide enhanced capabilities beyond standard promises.\r | |
16 | *\r | |
17 | * A Promise's `{@link #then then()}` method is used to specify onFulfilled and onRejected\r | |
18 | * callbacks that will be notified when the future value becomes available. Those callbacks\r | |
19 | * can subsequently transform the value that was resolved or the reason that was rejected.\r | |
20 | * Each call to `then` returns a new Promise of that transformed value; i.e., a Promise\r | |
21 | * that is resolved with the callback return value or rejected with any error thrown by\r | |
22 | * the callback.\r | |
23 | *\r | |
24 | * ## Basic Usage\r | |
25 | *\r | |
26 | * this.companyService.loadCompanies().then(\r | |
27 | * function (records) {\r | |
28 | * // Do something with result.\r | |
29 | * },\r | |
30 | * function (error) {\r | |
31 | * // Do something on failure.\r | |
32 | * }).\r | |
33 | * always(function () {\r | |
34 | * // Do something whether call succeeded or failed\r | |
35 | * });\r | |
36 | *\r | |
37 | * The above code uses the `Promise` returned from the `companyService.loadCompanies()`\r | |
38 | * method and uses `then()` to attach success and failure handlers. Finally, an `always()`\r | |
39 | * method call is chained onto the returned promise. This specifies a callback function\r | |
40 | * that will run whether the underlying call succeeded or failed.\r | |
41 | *\r | |
42 | * See `{@link Ext.Deferred}` for an example of using the returned Promise.\r | |
43 | *\r | |
44 | * [1]: http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts#april_14_2015_rev_38_final_draft\r | |
45 | *\r | |
46 | * @since 6.0.0\r | |
47 | */\r | |
48 | Ext.define('Ext.promise.Promise', function (ExtPromise) {\r | |
49 | var Deferred;\r | |
50 | \r | |
51 | return {\r | |
52 | requires: [\r | |
53 | 'Ext.promise.Deferred'\r | |
54 | ],\r | |
55 | \r | |
56 | statics: {\r | |
57 | /**\r | |
58 | * @property CancellationError\r | |
59 | * @static\r | |
60 | * The type of `Error` propagated by the `{@link #method-cancel}` method. If\r | |
61 | * the browser provides a native `CancellationError` then that type is used. If\r | |
62 | * not, a basic `Error` type is used.\r | |
63 | */\r | |
64 | CancellationError: Ext.global.CancellationError || Error,\r | |
65 | \r | |
66 | _ready: function () {\r | |
67 | // Our requires are met, so we can cache Ext.promise.Deferred\r | |
68 | Deferred = Ext.promise.Deferred;\r | |
69 | },\r | |
70 | \r | |
71 | /**\r | |
72 | * Returns a new Promise that will only resolve once all the specified\r | |
73 | * `promisesOrValues` have resolved.\r | |
74 | *\r | |
75 | * The resolution value will be an Array containing the resolution value of each\r | |
76 | * of the `promisesOrValues`.\r | |
77 | *\r | |
78 | * The public API's to use instead of this method are `{@link Ext.Promise#all}`\r | |
79 | * and `{@link Ext.Deferred#all}`.\r | |
80 | *\r | |
81 | * @param {Mixed[]/Ext.promise.Promise[]/Ext.promise.Promise} promisesOrValues An\r | |
82 | * Array of values or Promises, or a Promise of an Array of values or Promises.\r | |
83 | * @return {Ext.promise.Promise} A Promise of an Array of the resolved values.\r | |
84 | *\r | |
85 | * @static\r | |
86 | * @private\r | |
87 | */\r | |
88 | all: function (promisesOrValues) {\r | |
89 | //<debug>\r | |
90 | if (!(Ext.isArray(promisesOrValues) || ExtPromise.is(promisesOrValues))) {\r | |
91 | Ext.raise('Invalid parameter: expected an Array or Promise of an Array.');\r | |
92 | }\r | |
93 | //</debug>\r | |
94 | \r | |
95 | return ExtPromise.when(promisesOrValues).then(function (promisesOrValues) {\r | |
96 | var deferred = new Deferred(),\r | |
97 | remainingToResolve = promisesOrValues.length,\r | |
98 | results = new Array(remainingToResolve),\r | |
99 | index, promiseOrValue, resolve, i, len;\r | |
100 | \r | |
101 | if (!remainingToResolve) {\r | |
102 | deferred.resolve(results);\r | |
103 | }\r | |
104 | else {\r | |
105 | resolve = function (item, index) {\r | |
106 | return ExtPromise.when(item).then (function (value) {\r | |
107 | results[index] = value;\r | |
108 | \r | |
109 | if (!--remainingToResolve) {\r | |
110 | deferred.resolve(results);\r | |
111 | }\r | |
112 | \r | |
113 | return value;\r | |
114 | }, function (reason) {\r | |
115 | return deferred.reject(reason);\r | |
116 | });\r | |
117 | };\r | |
118 | \r | |
119 | for (index = i = 0, len = promisesOrValues.length; i < len; index = ++i) {\r | |
120 | promiseOrValue = promisesOrValues[index];\r | |
121 | \r | |
122 | if (index in promisesOrValues) {\r | |
123 | resolve(promiseOrValue, index);\r | |
124 | }\r | |
125 | else {\r | |
126 | remainingToResolve--;\r | |
127 | }\r | |
128 | }\r | |
129 | }\r | |
130 | \r | |
131 | return deferred.promise;\r | |
132 | });\r | |
133 | },\r | |
134 | \r | |
135 | /**\r | |
136 | * Determines whether the specified value is a Promise (including third-party\r | |
137 | * untrusted Promises or then()-ables), based on the Promises/A specification\r | |
138 | * feature test.\r | |
139 | *\r | |
140 | * @param {Mixed} value A potential Promise.\r | |
141 | * @return {Boolean} `true` if the given value is a Promise, otherwise `false`.\r | |
142 | * @static\r | |
143 | * @private\r | |
144 | */\r | |
145 | is: function (value) {\r | |
146 | return (Ext.isObject(value) || Ext.isFunction(value)) &&\r | |
147 | Ext.isFunction(value.then);\r | |
148 | },\r | |
149 | \r | |
150 | /**\r | |
151 | * Rethrows the specified Error on the next turn of the event loop.\r | |
152 | * @static\r | |
153 | * @private\r | |
154 | */\r | |
155 | rethrowError: function (error) {\r | |
156 | Ext.asap(function () {\r | |
157 | throw error;\r | |
158 | });\r | |
159 | },\r | |
160 | \r | |
161 | /**\r | |
162 | * Returns a new Promise that either\r | |
163 | *\r | |
164 | * * Resolves immediately for the specified value, or\r | |
165 | * * Resolves or rejects when the specified promise (or third-party Promise or\r | |
166 | * then()-able) is resolved or rejected.\r | |
167 | *\r | |
168 | * The public API's to use instead of this method are `{@link Ext.Promise#resolve}`\r | |
169 | * and `{@link Ext.Deferred#resolved}`.\r | |
170 | *\r | |
171 | * @param {Mixed} promiseOrValue A Promise (or third-party Promise or then()-able)\r | |
172 | * or value.\r | |
173 | * @return {Ext.Promise} A Promise of the specified Promise or value.\r | |
174 | *\r | |
175 | * @static\r | |
176 | * @private\r | |
177 | */\r | |
178 | when: function (value) {\r | |
179 | var deferred = new Ext.promise.Deferred();\r | |
180 | \r | |
181 | deferred.resolve(value);\r | |
182 | \r | |
183 | return deferred.promise;\r | |
184 | }\r | |
185 | },\r | |
186 | \r | |
187 | /**\r | |
188 | * @property {Ext.promise.Deferred} Reference to this promise's\r | |
189 | * `{@link Ext.promise.Deferred Deferred}` instance.\r | |
190 | *\r | |
191 | * @readonly\r | |
192 | * @private\r | |
193 | */\r | |
194 | owner: null,\r | |
195 | \r | |
196 | /**\r | |
197 | * NOTE: {@link Ext.promise.Deferred Deferreds} are the mechanism used to create new\r | |
198 | * Promises.\r | |
199 | * @param {Ext.promise.Deferred} owner The owning `Deferred` instance.\r | |
200 | *\r | |
201 | * @private\r | |
202 | */\r | |
203 | constructor: function (owner) {\r | |
204 | this.owner = owner;\r | |
205 | },\r | |
206 | \r | |
207 | /**\r | |
208 | * Attaches onFulfilled and onRejected callbacks that will be notified when the future\r | |
209 | * value becomes available.\r | |
210 | *\r | |
211 | * Those callbacks can subsequently transform the value that was fulfilled or the error\r | |
212 | * that was rejected. Each call to `then` returns a new Promise of that transformed\r | |
213 | * value; i.e., a Promise that is fulfilled with the callback return value or rejected\r | |
214 | * with any error thrown by the callback.\r | |
215 | *\r | |
216 | * @param {Function} onFulfilled Optional callback to execute to transform a\r | |
217 | * fulfillment value.\r | |
218 | * @param {Function} onRejected Optional callback to execute to transform a rejection\r | |
219 | * reason.\r | |
220 | * @param {Function} onProgress Optional callback function to be called with progress\r | |
221 | * updates.\r | |
222 | * @param {Object} scope Optional scope for the callback(s).\r | |
223 | * @return {Ext.promise.Promise} Promise that is fulfilled with the callback return\r | |
224 | * value or rejected with any error thrown by the callback.\r | |
225 | */\r | |
226 | then: function (onFulfilled, onRejected, onProgress, scope) {\r | |
227 | var ref;\r | |
228 | \r | |
229 | if (arguments.length === 1 && Ext.isObject(arguments[0])) {\r | |
230 | ref = arguments[0];\r | |
231 | onFulfilled = ref.success;\r | |
232 | onRejected = ref.failure;\r | |
233 | onProgress = ref.progress;\r | |
234 | scope = ref.scope;\r | |
235 | }\r | |
236 | \r | |
237 | if (scope) {\r | |
238 | if (onFulfilled) {\r | |
239 | onFulfilled = Ext.Function.bind(onFulfilled, scope);\r | |
240 | }\r | |
241 | \r | |
242 | if (onRejected) {\r | |
243 | onRejected = Ext.Function.bind(onRejected, scope);\r | |
244 | }\r | |
245 | \r | |
246 | if (onProgress) {\r | |
247 | onProgress = Ext.Function.bind(onProgress, scope);\r | |
248 | }\r | |
249 | }\r | |
250 | \r | |
251 | return this.owner.then(onFulfilled, onRejected, onProgress);\r | |
252 | },\r | |
253 | \r | |
254 | /**\r | |
255 | * Attaches an onRejected callback that will be notified if this Promise is rejected.\r | |
256 | *\r | |
257 | * The callback can subsequently transform the reason that was rejected. Each call to\r | |
258 | * `otherwise` returns a new Promise of that transformed value; i.e., a Promise that\r | |
259 | * is resolved with the original resolved value, or resolved with the callback return\r | |
260 | * value or rejected with any error thrown by the callback.\r | |
261 | *\r | |
262 | * @param {Function} onRejected Callback to execute to transform a rejection reason.\r | |
263 | * @param {Object} scope Optional scope for the callback.\r | |
264 | * @return {Ext.promise.Promise} Promise of the transformed future value.\r | |
265 | */\r | |
266 | otherwise: function (onRejected, scope) {\r | |
267 | var ref;\r | |
268 | \r | |
269 | if (arguments.length === 1 && Ext.isObject(arguments[0])) {\r | |
270 | ref = arguments[0];\r | |
271 | onRejected = ref.fn;\r | |
272 | scope = ref.scope;\r | |
273 | }\r | |
274 | \r | |
275 | if (scope != null) {\r | |
276 | onRejected = Ext.Function.bind(onRejected, scope);\r | |
277 | }\r | |
278 | \r | |
279 | return this.owner.then(null, onRejected);\r | |
280 | },\r | |
281 | \r | |
282 | /**\r | |
283 | * Attaches an onCompleted callback that will be notified when this Promise is completed.\r | |
284 | *\r | |
285 | * Similar to `finally` in `try... catch... finally`.\r | |
286 | *\r | |
287 | * NOTE: The specified callback does not affect the resulting Promise's outcome; any\r | |
288 | * return value is ignored and any Error is rethrown.\r | |
289 | *\r | |
290 | * @param {Function} onCompleted Callback to execute when the Promise is resolved or\r | |
291 | * rejected.\r | |
292 | * @param {Object} scope Optional scope for the callback.\r | |
293 | * @return {Ext.promise.Promise} A new "pass-through" Promise that is resolved with\r | |
294 | * the original value or rejected with the original reason.\r | |
295 | */\r | |
296 | always: function (onCompleted, scope) {\r | |
297 | var ref;\r | |
298 | \r | |
299 | if (arguments.length === 1 && Ext.isObject(arguments[0])) {\r | |
300 | ref = arguments[0];\r | |
301 | onCompleted = ref.fn;\r | |
302 | scope = ref.scope;\r | |
303 | }\r | |
304 | \r | |
305 | if (scope != null) {\r | |
306 | onCompleted = Ext.Function.bind(onCompleted, scope);\r | |
307 | }\r | |
308 | \r | |
309 | return this.owner.then(function (value) {\r | |
310 | try {\r | |
311 | onCompleted();\r | |
312 | }\r | |
313 | catch (e) {\r | |
314 | ExtPromise.rethrowError(e);\r | |
315 | }\r | |
316 | \r | |
317 | return value;\r | |
318 | }, function (reason) {\r | |
319 | try {\r | |
320 | onCompleted();\r | |
321 | }\r | |
322 | catch (e) {\r | |
323 | ExtPromise.rethrowError(e);\r | |
324 | }\r | |
325 | \r | |
326 | throw reason;\r | |
327 | });\r | |
328 | },\r | |
329 | \r | |
330 | /**\r | |
331 | * Terminates a Promise chain, ensuring that unhandled rejections will be rethrown as\r | |
332 | * Errors.\r | |
333 | *\r | |
334 | * One of the pitfalls of interacting with Promise-based APIs is the tendency for\r | |
335 | * important errors to be silently swallowed unless an explicit rejection handler is\r | |
336 | * specified.\r | |
337 | *\r | |
338 | * For example:\r | |
339 | *\r | |
340 | * promise.then(function () {\r | |
341 | * // logic in your callback throws an error and it is interpreted as a\r | |
342 | * // rejection. throw new Error("Boom!");\r | |
343 | * });\r | |
344 | *\r | |
345 | * // The Error was not handled by the Promise chain and is silently swallowed.\r | |
346 | *\r | |
347 | * This problem can be addressed by terminating the Promise chain with the done()\r | |
348 | * method:\r | |
349 | *\r | |
350 | * promise.then(function () {\r | |
351 | * // logic in your callback throws an error and it is interpreted as a\r | |
352 | * // rejection. throw new Error("Boom!");\r | |
353 | * }).done();\r | |
354 | *\r | |
355 | * // The Error was not handled by the Promise chain and is rethrown by done() on\r | |
356 | * // the next tick.\r | |
357 | *\r | |
358 | * The `done()` method ensures that any unhandled rejections are rethrown as Errors.\r | |
359 | */\r | |
360 | done: function () {\r | |
361 | this.owner.then(null, ExtPromise.rethrowError);\r | |
362 | },\r | |
363 | \r | |
364 | /**\r | |
365 | * Cancels this Promise if it is still pending, triggering a rejection with a\r | |
366 | * `{@link #CancellationError}` that will propagate to any Promises originating from\r | |
367 | * this Promise.\r | |
368 | *\r | |
369 | * NOTE: Cancellation only propagates to Promises that branch from the target Promise.\r | |
370 | * It does not traverse back up to parent branches, as this would reject nodes from\r | |
371 | * which other Promises may have branched, causing unintended side-effects.\r | |
372 | *\r | |
373 | * @param {Error} reason Cancellation reason.\r | |
374 | */\r | |
375 | cancel: function (reason) {\r | |
376 | if (reason == null) {\r | |
377 | reason = null;\r | |
378 | }\r | |
379 | \r | |
380 | this.owner.reject(new this.self.CancellationError(reason));\r | |
381 | },\r | |
382 | \r | |
383 | /**\r | |
384 | * Logs the resolution or rejection of this Promise with the specified category and\r | |
385 | * optional identifier. Messages are logged via all registered custom logger functions.\r | |
386 | *\r | |
387 | * @param {String} identifier An optional identifier to incorporate into the\r | |
388 | * resulting log entry.\r | |
389 | *\r | |
390 | * @return {Ext.promise.Promise} A new "pass-through" Promise that is resolved with\r | |
391 | * the original value or rejected with the original reason.\r | |
392 | */\r | |
393 | log: function (identifier) {\r | |
394 | if (identifier == null) {\r | |
395 | identifier = '';\r | |
396 | }\r | |
397 | \r | |
398 | return this._owner.then(function (value) {\r | |
399 | Ext.log("" + (identifier || 'Promise') + " resolved with value: " + value);\r | |
400 | \r | |
401 | return value;\r | |
402 | }, function (reason) {\r | |
403 | Ext.log("" + (identifier || 'Promise') + " rejected with reason: " + reason);\r | |
404 | \r | |
405 | throw reason;\r | |
406 | });\r | |
407 | }\r | |
408 | }},\r | |
409 | function (ExtPromise) {\r | |
410 | ExtPromise._ready();\r | |
411 | });\r |