|
8 | 8 | * called when the async task is fulfilled. |
9 | 9 | * |
10 | 10 | * <p>Typical usage would be like:<pre> |
11 | | - * query.findAsync().then(function(results) { |
| 11 | + * query.find().then(function(results) { |
12 | 12 | * results[0].set("foo", "bar"); |
13 | 13 | * return results[0].saveAsync(); |
14 | 14 | * }).then(function(result) { |
15 | 15 | * console.log("Updated " + result.id); |
16 | 16 | * }); |
17 | 17 | * </pre></p> |
18 | | - * |
19 | | - * @see AV.Promise.prototype.next |
| 18 | + * <p>Another example:<pre> |
| 19 | + * var promise = new AV.Promise(function(resolve, reject) { |
| 20 | + * resolve(42); |
| 21 | + * }); |
| 22 | + * promise.then(function(value){ |
| 23 | + * console.log(value); |
| 24 | + * }).catch(function(error){ |
| 25 | + * console.error(error); |
| 26 | + * }); |
| 27 | + * </pre></p> |
| 28 | + * @param {Function} fn An optional function with two arguments resolve |
| 29 | + * and reject.The first argument fulfills the promise, |
| 30 | + * the second argument rejects it. We can call these |
| 31 | + * functions, once our operation is completed. |
| 32 | + * @see AV.Promise.prototype.then |
20 | 33 | * @class |
21 | 34 | */ |
22 | | - AV.Promise = function() { |
| 35 | + AV.Promise = function(fn) { |
23 | 36 | this._resolved = false; |
24 | 37 | this._rejected = false; |
25 | 38 | this._resolvedCallbacks = []; |
26 | 39 | this._rejectedCallbacks = []; |
| 40 | + |
| 41 | + this.doResolve(fn); |
27 | 42 | }; |
28 | 43 |
|
29 | 44 | _.extend(AV.Promise, /** @lends AV.Promise */ { |
30 | 45 |
|
| 46 | + _isPromisesAPlusCompliant: false, |
| 47 | + |
31 | 48 | /** |
32 | 49 | * Returns true iff the given object fulfils the Promise interface. |
33 | 50 | * @return {Boolean} |
|
60 | 77 | * Returns a new promise that is fulfilled when all of the input promises |
61 | 78 | * are resolved. If any promise in the list fails, then the returned promise |
62 | 79 | * will fail with the last error. If they all succeed, then the returned |
63 | | - * promise will succeed, with the result being an array with the results of |
64 | | - * all the input promises. |
| 80 | + * promise will succeed, with the results being the results of all the input |
| 81 | + * promises. For example: <pre> |
| 82 | + * var p1 = AV.Promise.as(1); |
| 83 | + * var p2 = AV.Promise.as(2); |
| 84 | + * var p3 = AV.Promise.as(3); |
| 85 | + * |
| 86 | + * AV.Promise.when(p1, p2, p3).then(function(r1, r2, r3) { |
| 87 | + * console.log(r1); // prints 1 |
| 88 | + * console.log(r2); // prints 2 |
| 89 | + * console.log(r3); // prints 3 |
| 90 | + * });</pre> |
| 91 | + * |
| 92 | + * The input promises can also be specified as an array: <pre> |
| 93 | + * var promises = [p1, p2, p3]; |
| 94 | + * AV.Promise.when(promises).then(function(r1, r2, r3) { |
| 95 | + * console.log(r1); // prints 1 |
| 96 | + * console.log(r2); // prints 2 |
| 97 | + * console.log(r3); // prints 3 |
| 98 | + * }); |
| 99 | + * </pre> |
65 | 100 | * @param {Array} promises a list of promises to wait for. |
66 | 101 | * @return {AV.Promise} the new promise. |
67 | 102 | */ |
68 | | - when: function(promises) { |
| 103 | + when: function(promises, isAll) { |
69 | 104 | // Allow passing in Promises as separate arguments instead of an Array. |
70 | 105 | var objects; |
71 | 106 | if (promises && AV._isNullOrUndefined(promises.length)) { |
|
82 | 117 | errors.length = objects.length; |
83 | 118 |
|
84 | 119 | if (total === 0) { |
85 | | - return AV.Promise.as.apply(this, results); |
| 120 | + if(isAll) { |
| 121 | + return AV.Promise.as.call(this, results); |
| 122 | + } else { |
| 123 | + return AV.Promise.as.apply(this, results); |
| 124 | + } |
86 | 125 | } |
87 | 126 |
|
88 | 127 | var promise = new AV.Promise(); |
|
93 | 132 | if (hadError) { |
94 | 133 | promise.reject(errors); |
95 | 134 | } else { |
96 | | - promise.resolve.apply(promise, results); |
| 135 | + if(isAll) { |
| 136 | + promise.resolve.call(promise, results); |
| 137 | + } else { |
| 138 | + promise.resolve.apply(promise, results); |
| 139 | + } |
97 | 140 | } |
98 | 141 | } |
99 | 142 | }; |
|
117 | 160 | return promise; |
118 | 161 | }, |
119 | 162 |
|
| 163 | + /** |
| 164 | + * Returns a promise that resolves or rejects as soon as one |
| 165 | + * of the promises in the iterable resolves or rejects, with |
| 166 | + * the value or reason from that promise.Returns a new promise |
| 167 | + * that is fulfilled when one of the input promises. |
| 168 | + * For example: <pre> |
| 169 | + * var p1 = AV.Promise.as(1); |
| 170 | + * var p2 = AV.Promise.as(2); |
| 171 | + * var p3 = AV.Promise.as(3); |
| 172 | + * |
| 173 | + * AV.Promise.race(p1, p2, p3).then(function(result) { |
| 174 | + * console.log(result); // prints 1 |
| 175 | + * });</pre> |
| 176 | + * |
| 177 | + * The input promises can also be specified as an array: <pre> |
| 178 | + * var promises = [p1, p2, p3]; |
| 179 | + * AV.Promise.when(promises).then(function(result) { |
| 180 | + * console.log(result); // prints 1 |
| 181 | + * }); |
| 182 | + * </pre> |
| 183 | + * @param {Array} promises a list of promises to wait for. |
| 184 | + * @return {AV.Promise} the new promise. |
| 185 | + */ |
| 186 | + race: function(promises) { |
| 187 | + // Allow passing in Promises as separate arguments instead of an Array. |
| 188 | + var objects; |
| 189 | + if (promises && AV._isNullOrUndefined(promises.length)) { |
| 190 | + objects = arguments; |
| 191 | + } else { |
| 192 | + objects = promises; |
| 193 | + } |
| 194 | + |
| 195 | + var total = objects.length; |
| 196 | + var hadError = false; |
| 197 | + var results = []; |
| 198 | + var errors = []; |
| 199 | + |
| 200 | + results.length = errors.length = objects.length; |
| 201 | + |
| 202 | + if (total === 0) { |
| 203 | + return AV.Promise.as.call(this); |
| 204 | + } |
| 205 | + |
| 206 | + var promise = new AV.Promise(); |
| 207 | + |
| 208 | + var resolveOne = function(i) { |
| 209 | + if (!promise._resolved && !promise._rejected) { |
| 210 | + if (hadError) { |
| 211 | + promise.reject.call(promise, errors[i]); |
| 212 | + } else { |
| 213 | + promise.resolve.call(promise, results[i]); |
| 214 | + } |
| 215 | + } |
| 216 | + }; |
| 217 | + |
| 218 | + AV._arrayEach(objects, function(object, i) { |
| 219 | + if (AV.Promise.is(object)) { |
| 220 | + object.then(function(result) { |
| 221 | + results[i] = result; |
| 222 | + resolveOne(i); |
| 223 | + }, function(error) { |
| 224 | + errors[i] = error; |
| 225 | + hadError = true; |
| 226 | + resolveOne(i); |
| 227 | + }); |
| 228 | + } else { |
| 229 | + results[i] = object; |
| 230 | + resolveOne(i); |
| 231 | + } |
| 232 | + }); |
| 233 | + |
| 234 | + return promise; |
| 235 | + }, |
| 236 | + |
120 | 237 | /** |
121 | 238 | * Runs the given asyncFunction repeatedly, as long as the predicate |
122 | 239 | * function returns a truthy value. Stops repeating if asyncFunction returns |
|
134 | 251 | } |
135 | 252 | }); |
136 | 253 |
|
| 254 | + /** |
| 255 | + * Just like AV.Promise.when, but it calls resolveCallbck function |
| 256 | + * with one results array. |
| 257 | + * @see AV.Promise.when |
| 258 | + */ |
| 259 | + AV.Promise.all = function(promises) { |
| 260 | + return AV.Promise.when(promises, true); |
| 261 | + }; |
| 262 | + |
137 | 263 | _.extend(AV.Promise.prototype, /** @lends AV.Promise.prototype */ { |
138 | 264 |
|
139 | 265 | /** |
|
155 | 281 | this._rejectedCallbacks = []; |
156 | 282 | }, |
157 | 283 |
|
| 284 | + doResolve: function(fn){ |
| 285 | + if (!fn) return; |
| 286 | + var done = false; |
| 287 | + var self = this; |
| 288 | + try { |
| 289 | + fn(function (value) { |
| 290 | + if (done) return; |
| 291 | + done = true; |
| 292 | + self.resolve(value); |
| 293 | + }, function (reason) { |
| 294 | + if (done) return; |
| 295 | + done = true; |
| 296 | + self.reject(reason); |
| 297 | + }) |
| 298 | + } catch (ex) { |
| 299 | + if (done) return; |
| 300 | + done = true; |
| 301 | + self.reject(ex); |
| 302 | + } |
| 303 | + }, |
| 304 | + |
158 | 305 | /** |
159 | 306 | * Marks this promise as fulfilled, firing any callbacks waiting on it. |
160 | 307 | * @param {Object} error the error to pass to the callbacks. |
|
198 | 345 | var wrappedResolvedCallback = function() { |
199 | 346 | var result = arguments; |
200 | 347 | if (resolvedCallback) { |
201 | | - result = [resolvedCallback.apply(this, result)]; |
| 348 | + if (AV.Promise._isPromisesAPlusCompliant) { |
| 349 | + try { |
| 350 | + result = [resolvedCallback.apply(this, result)]; |
| 351 | + } catch (e) { |
| 352 | + result = [AV.Promise.error(e)]; |
| 353 | + } |
| 354 | + } else { |
| 355 | + result = [resolvedCallback.apply(this, result)]; |
| 356 | + } |
202 | 357 | } |
203 | 358 | if (result.length === 1 && AV.Promise.is(result[0])) { |
204 | 359 | result[0].then(function() { |
|
214 | 369 | var wrappedRejectedCallback = function(error) { |
215 | 370 | var result = []; |
216 | 371 | if (rejectedCallback) { |
217 | | - result = [rejectedCallback(error)]; |
| 372 | + if (AV.Promise._isPromisesAPlusCompliant) { |
| 373 | + try { |
| 374 | + result = [rejectedCallback(error)]; |
| 375 | + } catch (e) { |
| 376 | + result = [AV.Promise.error(e)]; |
| 377 | + } |
| 378 | + } else { |
| 379 | + result = [rejectedCallback(error)]; |
| 380 | + } |
218 | 381 | if (result.length === 1 && AV.Promise.is(result[0])) { |
219 | 382 | result[0].then(function() { |
220 | 383 | promise.resolve.apply(promise, arguments); |
221 | 384 | }, function(error) { |
222 | 385 | promise.reject(error); |
223 | 386 | }); |
224 | 387 | } else { |
225 | | - // A Promises/A+ compliant implementation would call: |
226 | | - // promise.resolve.apply(promise, result); |
227 | | - promise.reject(result[0]); |
| 388 | + if (AV.Promise._isPromisesAPlusCompliant) { |
| 389 | + promise.resolve.apply(promise, result); |
| 390 | + } else { |
| 391 | + promise.reject(result[0]); |
| 392 | + } |
228 | 393 | } |
229 | 394 | } else { |
230 | 395 | promise.reject(error); |
231 | 396 | } |
232 | 397 | }; |
233 | 398 |
|
| 399 | + var runLater = function(func) { |
| 400 | + func.call(); |
| 401 | + }; |
| 402 | + if (AV.Promise._isPromisesAPlusCompliant) { |
| 403 | + if (typeof(window) !== 'undefined' && window.setTimeout) { |
| 404 | + runLater = function(func) { |
| 405 | + window.setTimeout(func, 0); |
| 406 | + }; |
| 407 | + } else if (typeof(process) !== 'undefined' && process.nextTick) { |
| 408 | + runLater = function(func) { |
| 409 | + process.nextTick(func); |
| 410 | + }; |
| 411 | + } |
| 412 | + } |
| 413 | + |
| 414 | + var self = this; |
234 | 415 | if (this._resolved) { |
235 | | - wrappedResolvedCallback.apply(this, this._result); |
| 416 | + runLater(function() { |
| 417 | + wrappedResolvedCallback.apply(self, self._result); |
| 418 | + }); |
236 | 419 | } else if (this._rejected) { |
237 | | - wrappedRejectedCallback(this._error); |
| 420 | + runLater(function() { |
| 421 | + wrappedRejectedCallback(self._error); |
| 422 | + }); |
238 | 423 | } else { |
239 | 424 | this._resolvedCallbacks.push(wrappedResolvedCallback); |
240 | 425 | this._rejectedCallbacks.push(wrappedRejectedCallback); |
|
243 | 428 | return promise; |
244 | 429 | }, |
245 | 430 |
|
| 431 | + /** |
| 432 | + * Add handlers to be called when the Promise object is rejected. |
| 433 | + * |
| 434 | + * @param {Function} rejectedCallback Function that is called when this |
| 435 | + * Promise is rejected with an error. |
| 436 | + * @return {AV.Promise} A new Promise that will be fulfilled after this |
| 437 | + * Promise is fulfilled and either callback has completed. If the callback |
| 438 | + * returned a Promise, then this Promise will not be fulfilled until that |
| 439 | + * one is. |
| 440 | + * @function |
| 441 | + */ |
| 442 | + catch: function(onRejected) { |
| 443 | + return this.then(undefined, onRejected); |
| 444 | + }, |
| 445 | + |
| 446 | + /** |
| 447 | + * Add handlers to be called when the promise |
| 448 | + * is either resolved or rejected |
| 449 | + */ |
| 450 | + always: function(callback) { |
| 451 | + return this.then(callback, callback); |
| 452 | + }, |
| 453 | + |
| 454 | + /** |
| 455 | + * Add handlers to be called when the Promise object is resolved |
| 456 | + */ |
| 457 | + done: function(callback) { |
| 458 | + return this.then(callback); |
| 459 | + }, |
| 460 | + |
| 461 | + /** |
| 462 | + * Add handlers to be called when the Promise object is rejected |
| 463 | + */ |
| 464 | + fail: function(callback) { |
| 465 | + return this.then(null, callback); |
| 466 | + }, |
| 467 | + |
246 | 468 | /** |
247 | 469 | * Run the given callbacks after this promise is fulfilled. |
248 | 470 | * @param optionsOrCallback {} A Backbone-style options callback, or a |
|
313 | 535 |
|
314 | 536 | }); |
315 | 537 |
|
| 538 | + /** |
| 539 | + * Alias of AV.Promise.prototype.always |
| 540 | + * @function |
| 541 | + * @see AV.Promise#always |
| 542 | + */ |
| 543 | + AV.Promise.prototype.finally = AV.Promise.prototype.always; |
| 544 | + |
316 | 545 | }(this)); |
0 commit comments