Skip to content

Commit 392ceb4

Browse files
committed
Merge pull request #63 from killme2008/master
Fixed #34. Added AV.Promise#catch,AV.Promise.race etc.
2 parents 34ab294 + 38feab9 commit 392ceb4

File tree

4 files changed

+440
-17
lines changed

4 files changed

+440
-17
lines changed

lib/object.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,11 +460,13 @@
460460
if (_.isObject(value) &&
461461
!(value instanceof AV.Object) &&
462462
!(value instanceof AV.File)) {
463+
463464
value = value.toJSON ? value.toJSON() : value;
464465
var json = JSON.stringify(value);
465466
if (this._hashedJSON[key] !== json) {
467+
var wasSet = !! this._hashedJSON[key];
466468
this._hashedJSON[key] = json;
467-
return true;
469+
return wasSet;
468470
}
469471
}
470472
return false;

lib/promise.js

Lines changed: 245 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,43 @@
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);
2742
};
2843

2944
_.extend(AV.Promise, /** @lends AV.Promise */ {
3045

46+
_isPromisesAPlusCompliant: false,
47+
3148
/**
3249
* Returns true iff the given object fulfils the Promise interface.
3350
* @return {Boolean}
@@ -60,12 +77,30 @@
6077
* Returns a new promise that is fulfilled when all of the input promises
6178
* are resolved. If any promise in the list fails, then the returned promise
6279
* 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>
65100
* @param {Array} promises a list of promises to wait for.
66101
* @return {AV.Promise} the new promise.
67102
*/
68-
when: function(promises) {
103+
when: function(promises, isAll) {
69104
// Allow passing in Promises as separate arguments instead of an Array.
70105
var objects;
71106
if (promises && AV._isNullOrUndefined(promises.length)) {
@@ -82,7 +117,11 @@
82117
errors.length = objects.length;
83118

84119
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+
}
86125
}
87126

88127
var promise = new AV.Promise();
@@ -93,7 +132,11 @@
93132
if (hadError) {
94133
promise.reject(errors);
95134
} 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+
}
97140
}
98141
}
99142
};
@@ -117,6 +160,80 @@
117160
return promise;
118161
},
119162

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+
120237
/**
121238
* Runs the given asyncFunction repeatedly, as long as the predicate
122239
* function returns a truthy value. Stops repeating if asyncFunction returns
@@ -134,6 +251,15 @@
134251
}
135252
});
136253

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+
137263
_.extend(AV.Promise.prototype, /** @lends AV.Promise.prototype */ {
138264

139265
/**
@@ -155,6 +281,27 @@
155281
this._rejectedCallbacks = [];
156282
},
157283

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+
158305
/**
159306
* Marks this promise as fulfilled, firing any callbacks waiting on it.
160307
* @param {Object} error the error to pass to the callbacks.
@@ -198,7 +345,15 @@
198345
var wrappedResolvedCallback = function() {
199346
var result = arguments;
200347
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+
}
202357
}
203358
if (result.length === 1 && AV.Promise.is(result[0])) {
204359
result[0].then(function() {
@@ -214,27 +369,57 @@
214369
var wrappedRejectedCallback = function(error) {
215370
var result = [];
216371
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+
}
218381
if (result.length === 1 && AV.Promise.is(result[0])) {
219382
result[0].then(function() {
220383
promise.resolve.apply(promise, arguments);
221384
}, function(error) {
222385
promise.reject(error);
223386
});
224387
} 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+
}
228393
}
229394
} else {
230395
promise.reject(error);
231396
}
232397
};
233398

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;
234415
if (this._resolved) {
235-
wrappedResolvedCallback.apply(this, this._result);
416+
runLater(function() {
417+
wrappedResolvedCallback.apply(self, self._result);
418+
});
236419
} else if (this._rejected) {
237-
wrappedRejectedCallback(this._error);
420+
runLater(function() {
421+
wrappedRejectedCallback(self._error);
422+
});
238423
} else {
239424
this._resolvedCallbacks.push(wrappedResolvedCallback);
240425
this._rejectedCallbacks.push(wrappedRejectedCallback);
@@ -243,6 +428,43 @@
243428
return promise;
244429
},
245430

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+
246468
/**
247469
* Run the given callbacks after this promise is fulfilled.
248470
* @param optionsOrCallback {} A Backbone-style options callback, or a
@@ -313,4 +535,11 @@
313535

314536
});
315537

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+
316545
}(this));

0 commit comments

Comments
 (0)