]> git.proxmox.com Git - extjs.git/blame - extjs/packages/core/src/promise/Promise.js
add extjs 6.0.1 sources
[extjs.git] / extjs / packages / core / src / promise / Promise.js
CommitLineData
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
48Ext.define('Ext.promise.Promise', function (ExtPromise) {\r
49 var Deferred;\r
50\r
51return {\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
409function (ExtPromise) {\r
410 ExtPromise._ready();\r
411});\r