Skip to content

Commit

Permalink
Added Q.any(promisesArray) method.
Browse files Browse the repository at this point in the history
Returns a promise fulfilled with the value of the first resolved promise in promisesArray. If all promises in promisesArray are rejected, it returns a rejected promise.
  • Loading branch information
vergara committed Feb 21, 2015
1 parent d37583b commit 475850d
Show file tree
Hide file tree
Showing 3 changed files with 279 additions and 10 deletions.
10 changes: 8 additions & 2 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
<!-- vim:ts=4:sts=4:sw=4:et:tw=70 -->

## 1.2.0

- Added Q.any(promisesArray) method.
Returns a promise fulfilled with the value of the first resolved promise in
promisesArray. If all promises in promisesArray are rejected, it returns
a rejected promise.

## 1.1.2

- Removed extraneous files from the npm package by using the "files"
Expand Down Expand Up @@ -109,7 +116,7 @@ have been distributed far and wide and demand long term support.

## 0.9.4

- `isPromise` and `isPromiseAlike` now always returns a boolean
- `isPromise` and `isPromiseAlike` now always returns a boolean
(even for falsy values). #284 @lfac-pt
- Support for ES6 Generators in `async` #288 @andywingo
- Clear duplicate promise rejections from dispatch methods #238 @SLaks
Expand Down Expand Up @@ -759,4 +766,3 @@ Their replacements are listed here:
## 0.0.1

- initial version

54 changes: 50 additions & 4 deletions q.js
Original file line number Diff line number Diff line change
Expand Up @@ -1502,7 +1502,7 @@ Promise.prototype.keys = function () {
Q.all = all;
function all(promises) {
return when(promises, function (promises) {
var countDown = 0;
var pendingCount = 0;
var deferred = defer();
array_reduce(promises, function (undefined, promise, index) {
var snapshot;
Expand All @@ -1512,12 +1512,12 @@ function all(promises) {
) {
promises[index] = snapshot.value;
} else {
++countDown;
++pendingCount;
when(
promise,
function (value) {
promises[index] = value;
if (--countDown === 0) {
if (--pendingCount === 0) {
deferred.resolve(promises);
}
},
Expand All @@ -1528,7 +1528,7 @@ function all(promises) {
);
}
}, void 0);
if (countDown === 0) {
if (pendingCount === 0) {
deferred.resolve(promises);
}
return deferred.promise;
Expand All @@ -1539,6 +1539,52 @@ Promise.prototype.all = function () {
return all(this);
};

/**
* Returns the first resolved promise of an array. Prior rejected promises are
* ignored. Rejects only if all promises are rejected.
* @param {Array*} an array containing values or promises for values
* @returns a promise fulfilled with the value of the first resolved promise,
* or a rejected promise if all promises are rejected.
*/
Q.any = any;

function any(promises) {
if (promises.length === 0) {
return Q.resolve();
}

var deferred = Q.defer();
var pendingCount = 0;
array_reduce(promises, function(prev, current, index) {
var promise = promises[index];

pendingCount++;

when(promise, fulfilled, rejected, progress);
function fulfilled(result) {
deferred.resolve(result);
};
function rejected(reason) {
pendingCount--;
if (pendingCount === 0) {
deferred.reject(new Error("Can't get fulfillment value from any promise, all promises were rejected."));
}
};
function progress(progress) {
deferred.notify({
index: index,
value: progress
});
};
}, undefined);

return deferred.promise;
}

Promise.prototype.any = function() {
return any(this);
};

/**
* Waits for all promises to be settled, either fulfilled or
* rejected. This is distinct from `all` since that would stop
Expand Down
225 changes: 221 additions & 4 deletions spec/q-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,9 @@ describe("always next tick", function () {
spyOn(Q, 'nextTick').andCallFake(function immediateTick(task){
task();
});

Q.when(Q(), spy);

expect(spy).toHaveBeenCalled();
expect(Q.nextTick).toHaveBeenCalled();
});
Expand Down Expand Up @@ -1183,6 +1183,223 @@ describe("all", function () {

});

describe("any", function() {
it("fulfills when passed an empty array", function() {
return Q.any([]);
});

it("rejects after all promises are rejected", function() {
var deferreds = [Q.defer(), Q.defer()];
var promises = [deferreds[0].promise, deferreds[1].promise];

return testReject(promises, deferreds);
});

it("rejects after all promises in a sparse array are rejected", function() {
var deferreds = [Q.defer(), Q.defer()];
var promises = [];
promises[0] = deferreds[0].promise;
promises[3] = deferreds[1].promise;

return testReject(promises, deferreds);
});

function testReject(promises, deferreds) {
var promise = Q.any(promises);

for (var index = 0; index < deferreds.length; index++) {
var deferred = deferreds[index];
(function() {
deferred.reject(new Error('Rejected'));
})();
}

return Q.delay(250)
.then(function() {
expect(promise.isRejected()).toBe(true);
expect(promise.inspect().reason.message)
.toBe("Can't get fulfillment value from any promise, all promises were rejected.");
})
.timeout(1000);
}

it("fulfills with the first resolved promise", function() {
var deferreds = [Q.defer(), Q.defer()];
var promises = [deferreds[0].promise, deferreds[1].promise];

testFulfill(promises, deferreds);
});

it("fulfills when passed a sparse array", function() {
var deferreds = [Q.defer(), Q.defer()];
var promises = [];
promises[0] = deferreds[0].promise;
promises[2] = deferreds[1].promise;

testFulfill(promises, deferreds);
});

function testFulfill(promises, deferreds) {
var promise = Q.any(promises);

var j = 1;
for (var index = 0; index < deferreds.length; index++) {
var toResolve = deferreds[index];
if (!toResolve || !Q.isPromiseAlike(toResolve.promise)) {
continue;
}

(function(index, toResolve) {
var time = index * 50;
Q.delay(time).then(function() {
toResolve.resolve('Fulfilled' + index);
});
})(j, toResolve);

j++;
}

return Q.delay(400)
.then(function() {
expect(promise.isFulfilled()).toBe(true);
expect(promise.inspect().value).toBe('Fulfilled1');
})
.timeout(1000);
}

it("fulfills with the first value", function() {
var toResolve1 = Q.defer();
var toResolve2 = Q.defer();
var toResolve3 = Q.defer();
var promises = [toResolve1.promise, toResolve2.promise, 4, 5,
toResolve3.promise
];

var promise = Q.any(promises);

Q.delay(150).then(function() {
toResolve1.resolve(1);
});
Q.delay(50).then(function() {
toResolve2.resolve(2);
});
Q.delay(100).then(function() {
toResolve3.resolve(3);
});

return Q.delay(250)
.then(function() {
expect(promise.isFulfilled()).toBe(true);
expect(promise.inspect().value).toBe(4);
})
.timeout(1000);
});

it("fulfills after rejections", function() {
var toReject = [Q.defer(), Q.defer()];
var toResolve = Q.defer();
var promises = [toReject[0].promise, toReject[1].promise,
toResolve
.promise
];

var promise = Q.any(promises);

testFulfillAfterRejections(promises, toReject, toResolve);
});

it("fulfills after rejections in sparse array", function() {
var toReject = [Q.defer(), Q.defer()];
var toResolve = Q.defer();
var promises = [];
promises[2] = toReject[0].promise;
promises[5] = toReject[1].promise;
promises[9] = toResolve.promise;

testFulfillAfterRejections(promises, toReject, toResolve);
});

function testFulfillAfterRejections(promises, rejectDeferreds,
fulfillDeferred) {
var promise = Q.any(promises);

for (var index = 0; index < rejectDeferreds.length; index++) {
var toReject = rejectDeferreds[index];
(function(index, toReject) {
var time = (index + 1) * 50;
Q.delay(time).then(function() {
toReject.reject(new Error('Rejected'));
});
})(index, toReject);

index++;
}
Q.delay(index * 50).then(function() {
fulfillDeferred.resolve('Fulfilled');
});

return Q.delay(400)
.then(function() {
expect(promise.isFulfilled()).toBe(true);
expect(promise.inspect().value).toBe('Fulfilled');
})
.timeout(1000);
}

it("resolves foreign thenables", function() {
var normal = Q.delay(150)
.then(function() {})
.thenResolve(1);
var foreign = {
then: function(f) {
return f(2);
}
};

return Q.any([normal, foreign])
.then(function(result) {
expect(result).toEqual(2);
});
});

it("sends { index, value } progress updates", function() {
var deferred1 = Q.defer();
var deferred2 = Q.defer();

var progressValues = [];

Q.delay(50).then(function() {
deferred1.notify("a");
});
Q.delay(100).then(function() {
deferred2.notify("b");
deferred2.resolve();
});
Q.delay(150).then(function() {
deferred1.notify("c"); // Is lost, deferred2 already resolved.
deferred1.resolve();
});

return Q.any([deferred1.promise, deferred2.promise])
.delay(250)
.then(function() {
expect(progressValues).toEqual([{
index: 0,
value: "a"
}, {
index: 1,
value: "b"
}]);
},
undefined,
function(progressValue) {
progressValues.push(progressValue);
}
);
});

});

describe("allSettled", function () {
it("works on an empty array", function () {
return Q.allSettled([])
Expand Down Expand Up @@ -2117,7 +2334,7 @@ describe("node support", function () {
});

});

describe("npost", function () {

it("fulfills with callback result", function () {
Expand Down Expand Up @@ -2269,7 +2486,7 @@ describe("node support", function () {
expect(ten).toBe(10);
});
});

});

});
Expand Down

0 comments on commit 475850d

Please sign in to comment.