Skip to content

Commit d0373a3

Browse files
committed
Fixed #34. Added AV.Promise#catch,AV.Promise.race etc.
1 parent 34ab294 commit d0373a3

File tree

3 files changed

+386
-16
lines changed

3 files changed

+386
-16
lines changed

lib/promise.js

Lines changed: 248 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,44 @@
88
* called when the async task is fulfilled.
99
*
1010
* <p>Typical usage would be like:<pre>
11-
* query.findAsync().then(function(results) {
11+
* query.find().then(function(results) {
1212
* results[0].set("foo", "bar");
1313
* return results[0].saveAsync();
1414
* }).then(function(result) {
1515
* console.log("Updated " + result.id);
1616
* });
1717
* </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
2033
* @class
2134
*/
22-
AV.Promise = function() {
35+
AV.Promise = function(fn) {
2336
this._resolved = false;
2437
this._rejected = false;
2538
this._resolvedCallbacks = [];
2639
this._rejectedCallbacks = [];
40+
41+
this.doResolve(fn);
42+
return this;
2743
};
2844

2945
_.extend(AV.Promise, /** @lends AV.Promise */ {
3046

47+
_isPromisesAPlusCompliant: false,
48+
3149
/**
3250
* Returns true iff the given object fulfils the Promise interface.
3351
* @return {Boolean}
@@ -60,12 +78,30 @@
6078
* Returns a new promise that is fulfilled when all of the input promises
6179
* are resolved. If any promise in the list fails, then the returned promise
6280
* 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.
81+
* promise will succeed, with the results being the results of all the input
82+
* promises. For example: <pre>
83+
* var p1 = AV.Promise.as(1);
84+
* var p2 = AV.Promise.as(2);
85+
* var p3 = AV.Promise.as(3);
86+
*
87+
* AV.Promise.when(p1, p2, p3).then(function(r1, r2, r3) {
88+
* console.log(r1); // prints 1
89+
* console.log(r2); // prints 2
90+
* console.log(r3); // prints 3
91+
* });</pre>
92+
*
93+
* The input promises can also be specified as an array: <pre>
94+
* var promises = [p1, p2, p3];
95+
* AV.Promise.when(promises).then(function(r1, r2, r3) {
96+
* console.log(r1); // prints 1
97+
* console.log(r2); // prints 2
98+
* console.log(r3); // prints 3
99+
* });
100+
* </pre>
65101
* @param {Array} promises a list of promises to wait for.
66102
* @return {AV.Promise} the new promise.
67103
*/
68-
when: function(promises) {
104+
when: function(promises, isAll) {
69105
// Allow passing in Promises as separate arguments instead of an Array.
70106
var objects;
71107
if (promises && AV._isNullOrUndefined(promises.length)) {
@@ -82,7 +118,11 @@
82118
errors.length = objects.length;
83119

84120
if (total === 0) {
85-
return AV.Promise.as.apply(this, results);
121+
if(isAll) {
122+
return AV.Promise.as.call(this, results);
123+
} else {
124+
return AV.Promise.as.apply(this, results);
125+
}
86126
}
87127

88128
var promise = new AV.Promise();
@@ -93,7 +133,11 @@
93133
if (hadError) {
94134
promise.reject(errors);
95135
} else {
96-
promise.resolve.apply(promise, results);
136+
if(isAll) {
137+
promise.resolve.call(promise, results);
138+
} else {
139+
promise.resolve.apply(promise, results);
140+
}
97141
}
98142
}
99143
};
@@ -117,6 +161,82 @@
117161
return promise;
118162
},
119163

164+
/**
165+
* Returns a promise that resolves or rejects as soon as one
166+
* of the promises in the iterable resolves or rejects, with
167+
* the value or reason from that promise.Returns a new promise
168+
* that is fulfilled when one of the input promises.
169+
* For example: <pre>
170+
* var p1 = AV.Promise.as(1);
171+
* var p2 = AV.Promise.as(2);
172+
* var p3 = AV.Promise.as(3);
173+
*
174+
* AV.Promise.race(p1, p2, p3).then(function(result) {
175+
* console.log(result); // prints 1
176+
* });</pre>
177+
*
178+
* The input promises can also be specified as an array: <pre>
179+
* var promises = [p1, p2, p3];
180+
* AV.Promise.when(promises).then(function(result) {
181+
* console.log(result); // prints 1
182+
* });
183+
* </pre>
184+
* @param {Array} promises a list of promises to wait for.
185+
* @return {AV.Promise} the new promise.
186+
*/
187+
race: function(promises) {
188+
// Allow passing in Promises as separate arguments instead of an Array.
189+
var objects;
190+
if (promises && AV._isNullOrUndefined(promises.length)) {
191+
objects = arguments;
192+
} else {
193+
objects = promises;
194+
}
195+
196+
var total = objects.length;
197+
var hadError = false;
198+
var isResolved = false;
199+
var results = [];
200+
var errors = [];
201+
202+
results.length = errors.length = objects.length;
203+
204+
if (total === 0) {
205+
return AV.Promise.as.apply(this, result);
206+
}
207+
208+
var promise = new AV.Promise();
209+
210+
var resolveOne = function(i) {
211+
if (!isResolved) {
212+
isResolved = true;
213+
if (hadError) {
214+
promise.reject(errors[i]);
215+
} else {
216+
promise.resolve.call(promise, results[i]);
217+
}
218+
}
219+
};
220+
221+
AV._arrayEach(objects, function(object, i) {
222+
if (AV.Promise.is(object)) {
223+
object.then(function(result) {
224+
results[i] = result;
225+
resolveOne(i);
226+
}, function(error) {
227+
errors[i] = error;
228+
hadError = true;
229+
resolveOne(i);
230+
});
231+
} else {
232+
results[i] = object;
233+
resolveOne();
234+
}
235+
});
236+
237+
return promise;
238+
},
239+
120240
/**
121241
* Runs the given asyncFunction repeatedly, as long as the predicate
122242
* function returns a truthy value. Stops repeating if asyncFunction returns
@@ -134,6 +254,15 @@
134254
}
135255
});
136256

257+
/**
258+
* Just like AV.Promise.when, but it calls resolveCallbck function
259+
* with one results array.
260+
* @see AV.Promise.all
261+
*/
262+
AV.Promise.all = function(promises) {
263+
return AV.Promise.when(promises, true);
264+
};
265+
137266
_.extend(AV.Promise.prototype, /** @lends AV.Promise.prototype */ {
138267

139268
/**
@@ -155,6 +284,27 @@
155284
this._rejectedCallbacks = [];
156285
},
157286

287+
doResolve: function(fn){
288+
if (!fn) return;
289+
var done = false;
290+
var self = this;
291+
try {
292+
fn(function (value) {
293+
if (done) return;
294+
done = true;
295+
self.resolve(value);
296+
}, function (reason) {
297+
if (done) return;
298+
done = true;
299+
self.reject(reason);
300+
})
301+
} catch (ex) {
302+
if (done) return;
303+
done = true;
304+
self.reject(ex);
305+
}
306+
},
307+
158308
/**
159309
* Marks this promise as fulfilled, firing any callbacks waiting on it.
160310
* @param {Object} error the error to pass to the callbacks.
@@ -198,7 +348,15 @@
198348
var wrappedResolvedCallback = function() {
199349
var result = arguments;
200350
if (resolvedCallback) {
201-
result = [resolvedCallback.apply(this, result)];
351+
if (AV.Promise._isPromisesAPlusCompliant) {
352+
try {
353+
result = [resolvedCallback.apply(this, result)];
354+
} catch (e) {
355+
result = [AV.Promise.error(e)];
356+
}
357+
} else {
358+
result = [resolvedCallback.apply(this, result)];
359+
}
202360
}
203361
if (result.length === 1 && AV.Promise.is(result[0])) {
204362
result[0].then(function() {
@@ -214,27 +372,57 @@
214372
var wrappedRejectedCallback = function(error) {
215373
var result = [];
216374
if (rejectedCallback) {
217-
result = [rejectedCallback(error)];
375+
if (AV.Promise._isPromisesAPlusCompliant) {
376+
try {
377+
result = [rejectedCallback(error)];
378+
} catch (e) {
379+
result = [AV.Promise.error(e)];
380+
}
381+
} else {
382+
result = [rejectedCallback(error)];
383+
}
218384
if (result.length === 1 && AV.Promise.is(result[0])) {
219385
result[0].then(function() {
220386
promise.resolve.apply(promise, arguments);
221387
}, function(error) {
222388
promise.reject(error);
223389
});
224390
} else {
225-
// A Promises/A+ compliant implementation would call:
226-
// promise.resolve.apply(promise, result);
227-
promise.reject(result[0]);
391+
if (AV.Promise._isPromisesAPlusCompliant) {
392+
promise.resolve.apply(promise, result);
393+
} else {
394+
promise.reject(result[0]);
395+
}
228396
}
229397
} else {
230398
promise.reject(error);
231399
}
232400
};
233401

402+
var runLater = function(func) {
403+
func.call();
404+
};
405+
if (AV.Promise._isPromisesAPlusCompliant) {
406+
if (typeof(window) !== 'undefined' && window.setTimeout) {
407+
runLater = function(func) {
408+
window.setTimeout(func, 0);
409+
};
410+
} else if (typeof(process) !== 'undefined' && process.nextTick) {
411+
runLater = function(func) {
412+
process.nextTick(func);
413+
};
414+
}
415+
}
416+
417+
var self = this;
234418
if (this._resolved) {
235-
wrappedResolvedCallback.apply(this, this._result);
419+
runLater(function() {
420+
wrappedResolvedCallback.apply(self, self._result);
421+
});
236422
} else if (this._rejected) {
237-
wrappedRejectedCallback(this._error);
423+
runLater(function() {
424+
wrappedRejectedCallback(self._error);
425+
});
238426
} else {
239427
this._resolvedCallbacks.push(wrappedResolvedCallback);
240428
this._rejectedCallbacks.push(wrappedRejectedCallback);
@@ -243,6 +431,50 @@
243431
return promise;
244432
},
245433

434+
/**
435+
* Appends a rejection handler callback to the promise,
436+
* and returns a new promise resolving to the return value
437+
* of the callback if it is called, or to its original
438+
* fulfillment value if the promise is instead fulfilled.In fact,
439+
* it is equals to promise.then(undefined, onRejected).
440+
*
441+
* @see AV.Promise.prototype.then
442+
* @param {Function} onRejected Function that is called when this
443+
* Promise is rejected with an error. Once the callback is complete, then
444+
* the promise returned by "then" with be resolved successfully. If
445+
* rejectedCallback is null, or it returns a rejected Promise, then the
446+
* Promise returned by "then" will be rejected with that error.
447+
* @return {AV.Promise} A new Promise that will be fulfilled after this
448+
* Promise is fulfilled and either callback has completed. If the callback
449+
* returned a Promise, then this Promise will not be fulfilled until that
450+
* one is.
451+
*/
452+
catch: function(onRejected) {
453+
return this.then(undefined, onRejected);
454+
},
455+
456+
/**
457+
* Add handlers to be called when the promise
458+
* is either resolved or rejected
459+
*/
460+
always: function(callback) {
461+
return this.then(callback, callback);
462+
},
463+
464+
/**
465+
* Add handlers to be called when the Promise object is resolved
466+
*/
467+
done: function(callback) {
468+
return this.then(callback);
469+
},
470+
471+
/**
472+
* Add handlers to be called when the Promise object is rejected
473+
*/
474+
fail: function(callback) {
475+
return this.then(null, callback);
476+
},
477+
246478
/**
247479
* Run the given callbacks after this promise is fulfilled.
248480
* @param optionsOrCallback {} A Backbone-style options callback, or a

0 commit comments

Comments
 (0)