Skip to content

Commit 598ed9c

Browse files
author
Forbes Lindesay
committed
Support misbehaving promises in Promise.all
1 parent 5ff4fca commit 598ed9c

File tree

3 files changed

+67
-28
lines changed

3 files changed

+67
-28
lines changed

src/core.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ function Promise(fn) {
6464
if (fn === noop) return;
6565
doResolve(fn, this);
6666
}
67+
Promise._noop = noop;
6768

6869
Promise.prototype.then = function(onFulfilled, onRejected) {
6970
if (this.constructor !== Promise) {

src/es6-extensions.js

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,19 @@ module.exports = Promise;
99

1010
/* Static Functions */
1111

12-
function ValuePromise(value) {
13-
this.then = function (onFulfilled) {
14-
if (typeof onFulfilled !== 'function') return this;
15-
return new Promise(function (resolve, reject) {
16-
asap(function () {
17-
try {
18-
resolve(onFulfilled(value));
19-
} catch (ex) {
20-
reject(ex);
21-
}
22-
});
23-
});
24-
};
12+
var TRUE = valuePromise(true);
13+
var FALSE = valuePromise(false);
14+
var NULL = valuePromise(null);
15+
var UNDEFINED = valuePromise(undefined);
16+
var ZERO = valuePromise(0);
17+
var EMPTYSTRING = valuePromise('');
18+
19+
function valuePromise(value) {
20+
var p = new Promise(Promise._noop);
21+
p._state = 1;
22+
p._value = value;
23+
return p;
2524
}
26-
ValuePromise.prototype = Promise.prototype;
27-
28-
var TRUE = new ValuePromise(true);
29-
var FALSE = new ValuePromise(false);
30-
var NULL = new ValuePromise(null);
31-
var UNDEFINED = new ValuePromise(undefined);
32-
var ZERO = new ValuePromise(0);
33-
var EMPTYSTRING = new ValuePromise('');
34-
3525
Promise.resolve = function (value) {
3626
if (value instanceof Promise) return value;
3727

@@ -54,8 +44,7 @@ Promise.resolve = function (value) {
5444
});
5545
}
5646
}
57-
58-
return new ValuePromise(value);
47+
return valuePromise(value);
5948
};
6049

6150
Promise.all = function (arr) {
@@ -66,12 +55,25 @@ Promise.all = function (arr) {
6655
var remaining = args.length;
6756
function res(i, val) {
6857
if (val && (typeof val === 'object' || typeof val === 'function')) {
69-
var then = val.then;
70-
if (typeof then === 'function') {
71-
then.call(val, function (val) {
58+
if (val instanceof Promise && val.then === Promise.prototype.then) {
59+
while (val._state === 3) {
60+
val = val._value;
61+
}
62+
if (val._state === 1) return res(i, val._value);
63+
if (val._state === 2) reject(val._value);
64+
val.then(function (val) {
7265
res(i, val);
7366
}, reject);
7467
return;
68+
} else {
69+
var then = val.then;
70+
if (typeof then === 'function') {
71+
var p = new Promise(then.bind(val));
72+
p.then(function (val) {
73+
res(i, val);
74+
}, reject);
75+
return;
76+
}
7577
}
7678
}
7779
args[i] = val;

test/extensions-tests.js

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,15 +158,19 @@ describe('extensions', function () {
158158
})
159159
describe('of promises', function () {
160160
it('returns a promise for an array containing the fulfilled values', function (done) {
161-
var res = Promise.all([A, B, C])
161+
var d = {}
162+
var resolveD
163+
var res = Promise.all([A, B, C, new Promise(function (resolve) { resolveD = resolve })])
162164
assert(res instanceof Promise)
163165
res.then(function (res) {
164166
assert(Array.isArray(res))
165167
assert(res[0] === a)
166168
assert(res[1] === b)
167169
assert(res[2] === c)
170+
assert(res[3] === d)
168171
})
169172
.nodeify(done)
173+
resolveD(d)
170174
})
171175
})
172176
describe('of mixed values', function () {
@@ -195,6 +199,38 @@ describe('extensions', function () {
195199
.nodeify(done)
196200
})
197201
})
202+
describe('containing at least one eventually rejected promise', function () {
203+
it('rejects the resulting promise', function (done) {
204+
var rejectB
205+
var rejected = new Promise(function (resolve, reject) { rejectB = reject })
206+
var res = Promise.all([A, rejected, C])
207+
assert(res instanceof Promise)
208+
res.then(function (res) {
209+
throw new Error('Should be rejected')
210+
},
211+
function (err) {
212+
assert(err === rejection)
213+
})
214+
.nodeify(done)
215+
rejectB(rejection)
216+
})
217+
})
218+
describe('with a promise that resolves twice', function () {
219+
it('still waits for all the other promises', function (done) {
220+
var fakePromise = {then: function (onFulfilled) { onFulfilled(1); onFulfilled(2) }}
221+
var eventuallyRejected = {then: function (_, onRejected) { this.onRejected = onRejected }}
222+
var res = Promise.all([fakePromise, eventuallyRejected])
223+
assert(res instanceof Promise)
224+
res.then(function (res) {
225+
throw new Error('Should be rejected')
226+
},
227+
function (err) {
228+
assert(err === rejection)
229+
})
230+
.nodeify(done)
231+
eventuallyRejected.onRejected(rejection);
232+
})
233+
})
198234
})
199235
})
200236

0 commit comments

Comments
 (0)