]> git.proxmox.com Git - extjs.git/blame - extjs/packages/core/src/Deferred.js
add extjs 6.0.1 sources
[extjs.git] / extjs / packages / core / src / Deferred.js
CommitLineData
6527f429
DM
1/*\r
2 Ext.Deferred adapted from:\r
3 [DeftJS](https://github.com/deftjs/deftjs5)\r
4 Copyright (c) 2012-2013 [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 when(), all(), any(), some(), map(), reduce(), delay() and timeout()\r
8 sequence(), parallel(), pipeline()\r
9 methods adapted from: [when.js](https://github.com/cujojs/when)\r
10 Copyright (c) B Cavalier & J Hann\r
11 Open source under the [MIT License](http://en.wikipedia.org/wiki/MIT_License).\r
12 */\r
13\r
14/**\r
15 * Deferreds are the mechanism used to create new Promises. A Deferred has a single\r
16 * associated Promise that can be safely returned to external consumers to ensure they do\r
17 * not interfere with the resolution or rejection of the deferred operation.\r
18 *\r
19 * This implementation of Promises is an extension of the ECMAScript 6 Promises API as\r
20 * detailed [here][1]. For a compatible, though less full featured, API see `{@link Ext.Promise}`.\r
21 *\r
22 * A Deferred is typically used within the body of a function that performs an asynchronous\r
23 * operation. When that operation succeeds, the Deferred should be resolved; if that\r
24 * operation fails, the Deferred should be rejected.\r
25 *\r
26 * Each Deferred has an associated Promise. A Promise delegates `then` calls to its\r
27 * Deferred's `then` method. In this way, access to Deferred operations are divided between\r
28 * producer (Deferred) and consumer (Promise) roles.\r
29 *\r
30 * ## Basic Usage\r
31 *\r
32 * In it's most common form, a method will create and return a Promise like this:\r
33 *\r
34 * // A method in a service class which uses a Store and returns a Promise\r
35 * //\r
36 * loadCompanies: function () {\r
37 * var deferred = new Ext.Deferred(); // create the Ext.Deferred object\r
38 *\r
39 * this.companyStore.load({\r
40 * callback: function (records, operation, success) {\r
41 * if (success) {\r
42 * // Use "deferred" to drive the promise:\r
43 * deferred.resolve(records);\r
44 * }\r
45 * else {\r
46 * // Use "deferred" to drive the promise:\r
47 * deferred.reject("Error loading Companies.");\r
48 * }\r
49 * }\r
50 * });\r
51 *\r
52 * return deferred.promise; // return the Promise to the caller\r
53 * }\r
54 *\r
55 * You can see this method first creates a `{@link Ext.Deferred Deferred}` object. It then\r
56 * returns its `Promise` object for use by the caller. Finally, in the asynchronous\r
57 * callback, it resolves the `deferred` object if the call was successful, and rejects the\r
58 * `deferred` if the call failed.\r
59 *\r
60 * When a Deferred's `resolve` method is called, it fulfills with the optionally specified\r
61 * value. If `resolve` is called with a then-able (i.e.a Function or Object with a `then`\r
62 * function, such as another Promise) it assimilates the then-able's result; the Deferred\r
63 * provides its own `resolve` and `reject` methods as the onFulfilled or onRejected\r
64 * arguments in a call to that then-able's `then` function. If an error is thrown while\r
65 * calling the then-able's `then` function (prior to any call back to the specified\r
66 * `resolve` or `reject` methods), the Deferred rejects with that error. If a Deferred's\r
67 * `resolve` method is called with its own Promise, it rejects with a TypeError.\r
68 *\r
69 * When a Deferred's `reject` method is called, it rejects with the optionally specified\r
70 * reason.\r
71 *\r
72 * Each time a Deferred's `then` method is called, it captures a pair of optional\r
73 * onFulfilled and onRejected callbacks and returns a Promise of the Deferred's future\r
74 * value as transformed by those callbacks.\r
75 *\r
76 * See `{@link Ext.promise.Promise}` for an example of using the returned Promise.\r
77 *\r
78 * @since 6.0.0\r
79 */\r
80Ext.define('Ext.Deferred', function (Deferred) {\r
81 var ExtPromise,\r
82 when;\r
83\r
84return {\r
85 extend: 'Ext.promise.Deferred',\r
86\r
87 requires: [\r
88 'Ext.Promise'\r
89 ],\r
90\r
91 statics: {\r
92 _ready: function () {\r
93 // Our requires are met, so we can cache Ext.promise.Deferred\r
94 ExtPromise = Ext.promise.Promise;\r
95 when = Ext.Promise.resolve;\r
96 },\r
97\r
98 /**\r
99 * Returns a new Promise that will only resolve once all the specified\r
100 * `promisesOrValues` have resolved.\r
101 *\r
102 * The resolution value will be an Array containing the resolution value of each\r
103 * of the `promisesOrValues`.\r
104 *\r
105 * @param {Mixed[]/Ext.promise.Promise[]/Ext.promise.Promise} promisesOrValues An\r
106 * Array of values or Promises, or a Promise of an Array of values or Promises.\r
107 * @return {Ext.promise.Promise} A Promise of an Array of the resolved values.\r
108 * @static\r
109 */\r
110 all: function () {\r
111 return ExtPromise.all.apply(ExtPromise, arguments);\r
112 },\r
113\r
114 /**\r
115 * Initiates a competitive race, returning a new Promise that will resolve when\r
116 * any one of the specified `promisesOrValues` have resolved, or will reject when\r
117 * all `promisesOrValues` have rejected or cancelled.\r
118 *\r
119 * The resolution value will the first value of `promisesOrValues` to resolve.\r
120 *\r
121 * @param {Mixed[]/Ext.promise.Promise[]/Ext.promise.Promise} promisesOrValues An\r
122 * Array of values or Promises, or a Promise of an Array of values or Promises.\r
123 * @return {Ext.promise.Promise} A Promise of the first resolved value.\r
124 * @static\r
125 */\r
126 any: function (promisesOrValues) {\r
127 //<debug>\r
128 if (!(Ext.isArray(promisesOrValues) || ExtPromise.is(promisesOrValues))) {\r
129 Ext.raise('Invalid parameter: expected an Array or Promise of an Array.');\r
130 }\r
131 //</debug>\r
132\r
133 return Deferred.some(promisesOrValues, 1).then(function (array) {\r
134 return array[0];\r
135 }, function (error) {\r
136 if (error instanceof Error &&\r
137 error.message === 'Too few Promises were resolved.') {\r
138 Ext.raise('No Promises were resolved.');\r
139 }\r
140 else {\r
141 throw error;\r
142 }\r
143 });\r
144 },\r
145\r
146 /**\r
147 * Returns a new Promise that will automatically resolve with the specified\r
148 * Promise or value after the specified delay (in milliseconds).\r
149 *\r
150 * @param {Mixed} promiseOrValue A Promise or value.\r
151 * @param {Number} milliseconds A delay duration (in milliseconds).\r
152 * @return {Ext.promise.Promise} A Promise of the specified Promise or value that\r
153 * will resolve after the specified delay.\r
154 * @static\r
155 */\r
156 delay: function (promiseOrValue, milliseconds) {\r
157 var deferred;\r
158\r
159 if (arguments.length === 1) {\r
160 milliseconds = promiseOrValue;\r
161 promiseOrValue = undefined;\r
162 }\r
163\r
164 milliseconds = Math.max(milliseconds, 0);\r
165\r
166 deferred = new Deferred();\r
167\r
168 setTimeout(function () {\r
169 deferred.resolve(promiseOrValue);\r
170 }, milliseconds);\r
171\r
172 return deferred.promise;\r
173 },\r
174\r
175 /**\r
176 * Traditional map function, similar to `Array.prototype.map()`, that allows\r
177 * input to contain promises and/or values.\r
178 *\r
179 * The specified map function may return either a value or a promise.\r
180 *\r
181 * @param {Mixed[]/Ext.promise.Promise[]/Ext.promise.Promise} promisesOrValues An\r
182 * Array of values or Promises, or a Promise of an Array of values or Promises.\r
183 * @param {Function} mapFn A Function to call to transform each resolved value in\r
184 * the Array.\r
185 * @return {Ext.promise.Promise} A Promise of an Array of the mapped resolved\r
186 * values.\r
187 * @static\r
188 */\r
189 map: function (promisesOrValues, mapFn) {\r
190 //<debug>\r
191 if (!(Ext.isArray(promisesOrValues) || ExtPromise.is(promisesOrValues))) {\r
192 Ext.raise('Invalid parameter: expected an Array or Promise of an Array.');\r
193 }\r
194\r
195 if (!Ext.isFunction(mapFn)) {\r
196 Ext.raise('Invalid parameter: expected a function.');\r
197 }\r
198 //</debug>\r
199\r
200 return Deferred.resolved(promisesOrValues).then(function (promisesOrValues) {\r
201 var deferred, index, promiseOrValue, remainingToResolve, resolve, results, i, len;\r
202\r
203 remainingToResolve = promisesOrValues.length;\r
204 results = new Array(promisesOrValues.length);\r
205 deferred = new Deferred();\r
206\r
207 if (!remainingToResolve) {\r
208 deferred.resolve(results);\r
209 }\r
210 else {\r
211 resolve = function (item, index) {\r
212 return Deferred.resolved(item).then(function (value) {\r
213 return mapFn(value, index, results);\r
214 }).then(function (value) {\r
215 results[index] = value;\r
216 if (!--remainingToResolve) {\r
217 deferred.resolve(results);\r
218 }\r
219 return value;\r
220 }, function (reason) {\r
221 return deferred.reject(reason);\r
222 });\r
223 };\r
224 for (index = i = 0, len = promisesOrValues.length; i < len; index = ++i) {\r
225 promiseOrValue = promisesOrValues[index];\r
226\r
227 if (index in promisesOrValues) {\r
228 resolve(promiseOrValue, index);\r
229 }\r
230 else {\r
231 remainingToResolve--;\r
232 }\r
233 }\r
234 }\r
235\r
236 return deferred.promise;\r
237 });\r
238 },\r
239\r
240 /**\r
241 * Returns a new function that wraps the specified function and caches the\r
242 * results for previously processed inputs.\r
243 *\r
244 * Similar to {@link Ext.Function#memoize Ext.Function.memoize()}, except it\r
245 * allows for parameters that are Promises and/or values.\r
246 *\r
247 * @param {Function} fn A Function to wrap.\r
248 * @param {Object} scope An optional scope in which to execute the wrapped function.\r
249 * @param {Function} hashFn An optional function used to compute a hash key for\r
250 * storing the result, based on the arguments to the original function.\r
251 * @return {Function} The new wrapper function.\r
252 * @static\r
253 */\r
254 memoize: function (fn, scope, hashFn) {\r
255 var memoizedFn = Ext.Function.memoize(fn, scope, hashFn);\r
256\r
257 return function () {\r
258 return Deferred.all(Ext.Array.slice(arguments)).then(function (values) {\r
259 return memoizedFn.apply(scope, values);\r
260 });\r
261 };\r
262 },\r
263\r
264 /**\r
265 * Execute an Array (or {@link Ext.promise.Promise Promise} of an Array) of\r
266 * functions in parallel.\r
267 *\r
268 * The specified functions may optionally return their results as\r
269 * {@link Ext.promise.Promise Promises}.\r
270 *\r
271 * @param {Function[]/Ext.promise.Promise} fns The Array (or Promise of an Array)\r
272 * of functions to execute.\r
273 * @param {Object} scope Optional scope in which to execute the specified functions.\r
274 * @return {Ext.promise.Promise} Promise of an Array of results for each function\r
275 * call (in the same order).\r
276 * @static\r
277 */\r
278 parallel: function (fns, scope) {\r
279 if (scope == null) {\r
280 scope = null;\r
281 }\r
282\r
283 var args = Ext.Array.slice(arguments, 2);\r
284\r
285 return Deferred.map(fns, function (fn) {\r
286 if (!Ext.isFunction(fn)) {\r
287 throw new Error('Invalid parameter: expected a function.');\r
288 }\r
289\r
290 return fn.apply(scope, args);\r
291 });\r
292 },\r
293\r
294 /**\r
295 * Execute an Array (or {@link Ext.promise.Promise Promise} of an Array) of\r
296 * functions as a pipeline, where each function's result is passed to the\r
297 * subsequent function as input.\r
298 *\r
299 * The specified functions may optionally return their results as\r
300 * {@link Ext.promise.Promise Promises}.\r
301 *\r
302 * @param {Function[]/Ext.promise.Promise} fns The Array (or Promise of an Array)\r
303 * of functions to execute.\r
304 * @param {Object} initialValue Initial value to be passed to the first function\r
305 * in the pipeline.\r
306 * @param {Object} scope Optional scope in which to execute the specified functions.\r
307 * @return {Ext.promise.Promise} Promise of the result value for the final\r
308 * function in the pipeline.\r
309 * @static\r
310 */\r
311 pipeline: function (fns, initialValue, scope) {\r
312 if (scope == null) {\r
313 scope = null;\r
314 }\r
315\r
316 return Deferred.reduce(fns, function (value, fn) {\r
317 if (!Ext.isFunction(fn)) {\r
318 throw new Error('Invalid parameter: expected a function.');\r
319 }\r
320\r
321 return fn.call(scope, value);\r
322 }, initialValue);\r
323 },\r
324\r
325 /**\r
326 * Traditional reduce function, similar to `Array.reduce()`, that allows input to\r
327 * contain promises and/or values.\r
328 *\r
329 * @param {Mixed[]/Ext.promise.Promise[]/Ext.promise.Promise} values An\r
330 * Array of values or Promises, or a Promise of an Array of values or Promises.\r
331 * @param {Function} reduceFn A Function to call to transform each successive\r
332 * item in the Array into the final reduced value.\r
333 * @param {Mixed} initialValue An initial Promise or value.\r
334 * @return {Ext.promise.Promise} A Promise of the reduced value.\r
335 * @static\r
336 */\r
337 reduce: function (values, reduceFn, initialValue) {\r
338 //<debug>\r
339 if (!(Ext.isArray(values) || ExtPromise.is(values))) {\r
340 Ext.raise('Invalid parameter: expected an Array or Promise of an Array.');\r
341 }\r
342\r
343 if (!Ext.isFunction(reduceFn)) {\r
344 Ext.raise('Invalid parameter: expected a function.');\r
345 }\r
346 //</debug>\r
347\r
348 var initialValueSpecified = arguments.length === 3;\r
349\r
350 return Deferred.resolved(values).then(function (promisesOrValues) {\r
351 var reduceArguments = [\r
352 promisesOrValues,\r
353 function (previousValueOrPromise, currentValueOrPromise, currentIndex) {\r
354 return Deferred.resolved(previousValueOrPromise).then(function (previousValue) {\r
355 return Deferred.resolved(currentValueOrPromise).then(function (currentValue) {\r
356 return reduceFn(previousValue, currentValue, currentIndex, promisesOrValues);\r
357 });\r
358 });\r
359 }\r
360 ];\r
361\r
362 if (initialValueSpecified) {\r
363 reduceArguments.push(initialValue);\r
364 }\r
365\r
366 return Ext.Array.reduce.apply(Ext.Array, reduceArguments);\r
367 });\r
368 },\r
369\r
370 /**\r
371 * Convenience method that returns a new Promise rejected with the specified\r
372 * reason.\r
373 *\r
374 * @param {Error} reason Rejection reason.\r
375 * @return {Ext.promise.Promise} The rejected Promise.\r
376 * @static\r
377 */\r
378 rejected: function (reason) {\r
379 var deferred = new Ext.Deferred();\r
380\r
381 deferred.reject(reason);\r
382\r
383 return deferred.promise;\r
384 },\r
385\r
386 /**\r
387 * Returns a new Promise that either\r
388 *\r
389 * * Resolves immediately for the specified value, or\r
390 * * Resolves or rejects when the specified promise (or third-party Promise or\r
391 * then()-able) is resolved or rejected.\r
392 *\r
393 * @param {Mixed} promiseOrValue A Promise (or third-party Promise or then()-able)\r
394 * or value.\r
395 * @return {Ext.promise.Promise} A Promise of the specified Promise or value.\r
396 * @static\r
397 */\r
398 resolved: function (value) {\r
399 var deferred = new Ext.Deferred();\r
400\r
401 deferred.resolve(value);\r
402\r
403 return deferred.promise;\r
404 },\r
405\r
406 /**\r
407 * Execute an Array (or {@link Ext.promise.Promise Promise} of an Array) of\r
408 * functions sequentially.\r
409 *\r
410 * The specified functions may optionally return their results as {@link\r
411 * Ext.promise.Promise Promises}.\r
412 *\r
413 * @param {Function[]/Ext.promise.Promise} fns The Array (or Promise of an Array)\r
414 * of functions to execute.\r
415 * @param {Object} scope Optional scope in which to execute the specified functions.\r
416 * @return {Ext.promise.Promise} Promise of an Array of results for each function\r
417 * call (in the same order).\r
418 * @static\r
419 */\r
420 sequence: function (fns, scope) {\r
421 if (scope == null) {\r
422 scope = null;\r
423 }\r
424\r
425 var args = Ext.Array.slice(arguments, 2);\r
426\r
427 return Deferred.reduce(fns, function (results, fn) {\r
428 if (!Ext.isFunction(fn)) {\r
429 throw new Error('Invalid parameter: expected a function.');\r
430 }\r
431\r
432 return Deferred.resolved(fn.apply(scope, args)).then(function (result) {\r
433 results.push(result);\r
434\r
435 return results;\r
436 });\r
437 }, []);\r
438 },\r
439\r
440 /**\r
441 * Initiates a competitive race, returning a new Promise that will resolve when\r
442 * `howMany` of the specified `promisesOrValues` have resolved, or will reject\r
443 * when it becomes impossible for `howMany` to resolve.\r
444 *\r
445 * The resolution value will be an Array of the first `howMany` values of\r
446 * `promisesOrValues` to resolve.\r
447 *\r
448 * @param {Mixed[]/Ext.promise.Promise[]/Ext.promise.Promise} promisesOrValues An\r
449 * Array of values or Promises, or a Promise of an Array of values or Promises.\r
450 * @param {Number} howMany The expected number of resolved values.\r
451 * @return {Ext.promise.Promise} A Promise of the expected number of resolved\r
452 * values.\r
453 * @static\r
454 */\r
455 some: function (promisesOrValues, howMany) {\r
456 //<debug>\r
457 if (!(Ext.isArray(promisesOrValues) || ExtPromise.is(promisesOrValues))) {\r
458 Ext.raise('Invalid parameter: expected an Array or Promise of an Array.');\r
459 }\r
460\r
461 if (!Ext.isNumeric(howMany) || howMany <= 0) {\r
462 Ext.raise('Invalid parameter: expected a positive integer.');\r
463 }\r
464 //</debug>\r
465\r
466 return Deferred.resolved(promisesOrValues).then(function (promisesOrValues) {\r
467 var deferred, index, onReject, onResolve, promiseOrValue,\r
468 remainingToReject, remainingToResolve, values, i, len;\r
469\r
470 values = [];\r
471 remainingToResolve = howMany;\r
472 remainingToReject = (promisesOrValues.length - remainingToResolve) + 1;\r
473 deferred = new Deferred();\r
474\r
475 if (promisesOrValues.length < howMany) {\r
476 deferred.reject(new Error('Too few Promises were resolved.'));\r
477 }\r
478 else {\r
479 onResolve = function (value) {\r
480 if (remainingToResolve > 0) {\r
481 values.push(value);\r
482 }\r
483\r
484 remainingToResolve--;\r
485\r
486 if (remainingToResolve === 0) {\r
487 deferred.resolve(values);\r
488 }\r
489\r
490 return value;\r
491 };\r
492\r
493 onReject = function (reason) {\r
494 remainingToReject--;\r
495\r
496 if (remainingToReject === 0) {\r
497 deferred.reject(new Error('Too few Promises were resolved.'));\r
498 }\r
499\r
500 return reason;\r
501 };\r
502\r
503 for (index = i = 0, len = promisesOrValues.length; i < len; index = ++i) {\r
504 promiseOrValue = promisesOrValues[index];\r
505\r
506 if (index in promisesOrValues) {\r
507 Deferred.resolved(promiseOrValue).then(onResolve, onReject);\r
508 }\r
509 }\r
510 }\r
511\r
512 return deferred.promise;\r
513 });\r
514 },\r
515\r
516 /**\r
517 * Returns a new Promise that will automatically reject after the specified\r
518 * timeout (in milliseconds) if the specified promise has not resolved or\r
519 * rejected.\r
520 *\r
521 * @param {Mixed} promiseOrValue A Promise or value.\r
522 * @param {Number} milliseconds A timeout duration (in milliseconds).\r
523 * @return {Ext.promise.Promise} A Promise of the specified Promise or value that\r
524 * enforces the specified timeout.\r
525 * @static\r
526 */\r
527 timeout: function (promiseOrValue, milliseconds) {\r
528 var deferred = new Deferred(),\r
529 timeoutId;\r
530\r
531 timeoutId = setTimeout(function () {\r
532 if (timeoutId) {\r
533 deferred.reject(new Error('Promise timed out.'));\r
534 }\r
535 }, milliseconds);\r
536\r
537 Deferred.resolved(promiseOrValue).then(function (value) {\r
538 clearTimeout(timeoutId);\r
539 timeoutId = null;\r
540 deferred.resolve(value);\r
541 }, function (reason) {\r
542 clearTimeout(timeoutId);\r
543 timeoutId = null;\r
544 deferred.reject(reason);\r
545 });\r
546\r
547 return deferred.promise;\r
548 }\r
549 }\r
550}},\r
551function (Deferred) {\r
552 Deferred._ready();\r
553});\r