Skip to content

Commit 51d30a5

Browse files
committed
Merge pull request #187 from ParsePlatform/promises_es6
Implement Promise.catch, Promise.all, Promise.race
2 parents 0b3f7ad + 984a095 commit 51d30a5

File tree

2 files changed

+311
-0
lines changed

2 files changed

+311
-0
lines changed

src/ParsePromise.js

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,12 +207,21 @@ export default class ParsePromise {
207207

208208
/**
209209
* Add handlers to be called when the Promise object is rejected
210+
* Alias for catch().
210211
* @method fail
211212
*/
212213
fail(callback) {
213214
return this.then(null, callback);
214215
}
215216

217+
/**
218+
* Add handlers to be called when the Promise object is rejected
219+
* @method catch
220+
*/
221+
catch(callback) {
222+
return this.then(null, callback);
223+
}
224+
216225
/**
217226
* Run the given callbacks after this promise is fulfilled.
218227
* @method _thenRunCallbacks
@@ -435,6 +444,115 @@ export default class ParsePromise {
435444
return promise;
436445
}
437446

447+
/**
448+
* Returns a new promise that is fulfilled when all of the promises in the
449+
* iterable argument are resolved. If any promise in the list fails, then
450+
* the returned promise will be immediately rejected with the reason that
451+
* single promise rejected. If they all succeed, then the returned promise
452+
* will succeed, with the results being the results of all the input
453+
* promises. If the iterable provided is empty, the returned promise will
454+
* be immediately resolved.
455+
*
456+
* For example: <pre>
457+
* var p1 = Parse.Promise.as(1);
458+
* var p2 = Parse.Promise.as(2);
459+
* var p3 = Parse.Promise.as(3);
460+
*
461+
* Parse.Promise.all([p1, p2, p3]).then(function([r1, r2, r3]) {
462+
* console.log(r1); // prints 1
463+
* console.log(r2); // prints 2
464+
* console.log(r3); // prints 3
465+
* });</pre>
466+
*
467+
* @method all
468+
* @param {Iterable} promises an iterable of promises to wait for.
469+
* @static
470+
* @return {Parse.Promise} the new promise.
471+
*/
472+
static all(promises) {
473+
let total = 0;
474+
let objects = [];
475+
476+
for (let p of promises) {
477+
objects[total++] = p;
478+
}
479+
480+
if (total === 0) {
481+
return ParsePromise.as([]);
482+
}
483+
484+
let hadError = false;
485+
let promise = new ParsePromise();
486+
let resolved = 0;
487+
let results = [];
488+
objects.forEach((object, i) => {
489+
if (ParsePromise.is(object)) {
490+
object.then((result) => {
491+
if (hadError) {
492+
return false;
493+
}
494+
results[i] = result;
495+
resolved++;
496+
if (resolved >= total) {
497+
promise.resolve(results);
498+
}
499+
}, (error) => {
500+
// Reject immediately
501+
promise.reject(error);
502+
hadError = true;
503+
});
504+
} else {
505+
results[i] = object;
506+
resolved++;
507+
if (!hadError && resolved >= total) {
508+
promise.resolve(results);
509+
}
510+
}
511+
});
512+
513+
return promise;
514+
}
515+
516+
/**
517+
* Returns a new promise that is immediately fulfilled when any of the
518+
* promises in the iterable argument are resolved or rejected. If the
519+
* first promise to complete is resolved, the returned promise will be
520+
* resolved with the same value. Likewise, if the first promise to
521+
* complete is rejected, the returned promise will be rejected with the
522+
* same reason.
523+
*
524+
* @method race
525+
* @param {Iterable} promises an iterable of promises to wait for.
526+
* @static
527+
* @return {Parse.Promise} the new promise.
528+
*/
529+
static race(promises) {
530+
let completed = false;
531+
let promise = new ParsePromise();
532+
for (let p of promises) {
533+
if (ParsePromise.is(p)) {
534+
p.then((result) => {
535+
if (completed) {
536+
return;
537+
}
538+
completed = true;
539+
promise.resolve(result);
540+
}, (error) => {
541+
if (completed) {
542+
return;
543+
}
544+
completed = true;
545+
promise.reject(error);
546+
});
547+
} else if (!completed) {
548+
completed = true;
549+
promise.resolve(p);
550+
}
551+
}
552+
553+
return promise;
554+
}
555+
438556
/**
439557
* Runs the given asyncFunction repeatedly, as long as the predicate
440558
* function returns a truthy value. Stops repeating if asyncFunction returns

src/__tests__/ParsePromise-test.js

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ var ParsePromise = require('../ParsePromise');
1414
var asyncHelper = require('./test_helpers/asyncHelper');
1515

1616
describe('Promise', () => {
17+
it('can disable A+ compliance', () => {
18+
ParsePromise.disableAPlusCompliant();
19+
expect(ParsePromise.isPromisesAPlusCompliant()).toBe(false);
20+
});
21+
22+
it('can enable A+ compliance', () => {
23+
ParsePromise.enableAPlusCompliant();
24+
expect(ParsePromise.isPromisesAPlusCompliant()).toBe(true);
25+
});
26+
});
27+
28+
function promiseTests() {
1729
it('can be initially resolved', () => {
1830
var promise = ParsePromise.as('foo');
1931
promise.then((result) => {
@@ -297,6 +309,27 @@ describe('Promise', () => {
297309
jest.runAllTimers();
298310
}));
299311

312+
it('immediately resolves when processing an empty array in parallel', asyncHelper((done) => {
313+
ParsePromise.when([]).then((result) => {
314+
expect(result).toEqual([]);
315+
done();
316+
});
317+
}));
318+
319+
it('can handle rejected promises in parallel', asyncHelper((done) => {
320+
ParsePromise.when([ParsePromise.as(1), ParsePromise.error('an error')]).then(null, (errors) => {
321+
expect(errors).toEqual([undefined, 'an error']);
322+
done();
323+
});
324+
}));
325+
326+
it('can automatically resolve non-promises in parallel', asyncHelper((done) => {
327+
ParsePromise.when([1,2,3]).then((results) => {
328+
expect(results).toEqual([1,2,3]);
329+
done();
330+
});
331+
}));
332+
300333
it('passes on errors', () => {
301334
ParsePromise.error('foo').then(() => {
302335
// This should not be reached
@@ -428,6 +461,40 @@ describe('Promise', () => {
428461
});
429462
});
430463

464+
it('runs catch callbacks on error', () => {
465+
var promise = ParsePromise.error('foo');
466+
promise.fail((error) => {
467+
expect(error).toBe('foo');
468+
}).then((result) => {
469+
if (ParsePromise.isPromisesAPlusCompliant()) {
470+
expect(result).toBe(undefined);
471+
} else {
472+
// This should not be reached
473+
expect(true).toBe(false);
474+
}
475+
}, (error) => {
476+
if (ParsePromise.isPromisesAPlusCompliant()) {
477+
// This should not be reached
478+
expect(true).toBe(false);
479+
} else {
480+
expect(error).toBe(undefined);
481+
}
482+
});
483+
});
484+
485+
it('does not run catch callbacks on success', () => {
486+
var promise = ParsePromise.as('foo');
487+
promise.catch((error) => {
488+
// This should not be reached
489+
expect(true).toBe(false);
490+
}).then((result) => {
491+
expect(result).toBe('foo');
492+
}, (error) => {
493+
// This should not be reached
494+
expect(true).toBe(false);
495+
});
496+
});
497+
431498
it('operates asynchonously', () => {
432499
var triggered = false;
433500
ParsePromise.as().then(() => {
@@ -518,4 +585,130 @@ describe('Promise', () => {
518585
done();
519586
});
520587
}));
588+
589+
it('resolves Promise.all with the set of resolved results', asyncHelper((done) => {
590+
let firstSet = [
591+
new ParsePromise(),
592+
new ParsePromise(),
593+
new ParsePromise(),
594+
new ParsePromise()
595+
];
596+
597+
let secondSet = [5,6,7];
598+
599+
ParsePromise.all([]).then((results) => {
600+
expect(results).toEqual([]);
601+
602+
return ParsePromise.all(firstSet);
603+
}).then((results) => {
604+
expect(results).toEqual([1,2,3,4]);
605+
return ParsePromise.all(secondSet);
606+
}).then((results) => {
607+
expect(results).toEqual([5,6,7]);
608+
done();
609+
});
610+
firstSet[0].resolve(1);
611+
firstSet[1].resolve(2);
612+
firstSet[2].resolve(3);
613+
firstSet[3].resolve(4);
614+
}));
615+
616+
it('rejects Promise.all with the first rejected promise', asyncHelper((done) => {
617+
let promises = [
618+
new ParsePromise(),
619+
new ParsePromise(),
620+
new ParsePromise()
621+
];
622+
623+
ParsePromise.all(promises).then(() => {
624+
// this should not be reached
625+
}, (error) => {
626+
expect(error).toBe('an error');
627+
done();
628+
});
629+
promises[0].resolve(1);
630+
promises[1].reject('an error');
631+
promises[2].resolve(3);
632+
}));
633+
634+
it('resolves Promise.race with the first resolved result', asyncHelper((done) => {
635+
let firstSet = [
636+
new ParsePromise(),
637+
new ParsePromise(),
638+
new ParsePromise()
639+
];
640+
641+
let secondSet = [4, 5, ParsePromise.error()];
642+
643+
ParsePromise.race(firstSet).then((result) => {
644+
expect(result).toBe(2);
645+
646+
return ParsePromise.race(secondSet);
647+
}).then((result) => {
648+
expect(result).toBe(4);
649+
done();
650+
});
651+
firstSet[1].resolve(2);
652+
firstSet[0].resolve(1);
653+
}));
654+
655+
it('rejects Promise.race with the first rejected reason', asyncHelper((done) => {
656+
let promises = [
657+
new ParsePromise(),
658+
new ParsePromise(),
659+
new ParsePromise()
660+
];
661+
662+
ParsePromise.race(promises).fail((error) => {
663+
expect(error).toBe('error 2');
664+
done();
665+
});
666+
promises[1].reject('error 2');
667+
promises[0].resolve('error 1');
668+
}));
669+
670+
it('can implement continuations', asyncHelper((done) => {
671+
let count = 0;
672+
let loop = () => {
673+
count++;
674+
return ParsePromise.as();
675+
}
676+
ParsePromise._continueWhile(
677+
() => { return count < 5 },
678+
loop
679+
).then(() => {
680+
expect(count).toBe(5);
681+
done();
682+
});
683+
}));
684+
685+
it('can attach a universal callback to a promise', asyncHelper((done) => {
686+
ParsePromise.as(15)._continueWith((result, err) => {
687+
expect(result).toEqual([15]);
688+
expect(err).toBe(null);
689+
690+
ParsePromise.error('an error')._continueWith((result, err) => {
691+
expect(result).toBe(null);
692+
expect(err).toBe('an error');
693+
694+
done();
695+
});
696+
});
697+
}));
698+
}
699+
700+
describe('Promise (A Compliant)', () => {
701+
beforeEach(() => {
702+
ParsePromise.disableAPlusCompliant();
703+
});
704+
705+
promiseTests();
706+
});
707+
708+
describe('Promise (A+ Compliant)', () => {
709+
beforeEach(() => {
710+
ParsePromise.enableAPlusCompliant();
711+
});
712+
713+
promiseTests();
521714
});

0 commit comments

Comments
 (0)