From b006aa9d1270fa89538ff9e4f25534352e1116a6 Mon Sep 17 00:00:00 2001 From: Ketan Bhatt Date: Mon, 13 Jul 2015 13:37:34 +0530 Subject: [PATCH] Added .any() to kew, with tests and docs --- .gitignore | 4 +- README.md | 32 +++++++++++ kew.js | 55 ++++++++++++++++++ test/static.js | 151 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 241 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7dccd97..689c040 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,6 @@ logs results node_modules -npm-debug.log \ No newline at end of file +npm-debug.log + +.idea/ \ No newline at end of file diff --git a/README.md b/README.md index 7bd4735..e84baa2 100644 --- a/README.md +++ b/README.md @@ -220,6 +220,38 @@ Q.all(promises) }) ``` +### `.any()` for first of many things + +A common use-case is when you want to respond to the first promise that returns. In that case, you may pass them into `.any()` which will create a promise that resolves successfully with the result of the first resolved promise: + +```javascript +var promises = [] +promises.push(getUrlContent(url1)) +promises.push(getUrlContent(url2)) +promises.push(getUrlContent(url3)) + +Q.any(promises) + .then(function (content) { + // content === content from the first URL to be resolved + }) +``` + +If all of the promises fail, Q.any will fail and return an array containing errors from each promise: + +```javascript +var promises = [] +promises.push(getUrlContent(url1)) +promises.push(getUrlContent(url2)) +promises.push(getUrlContent(url3)) + +Q.all(promises) + .fail(function (e) { + console.log("First URL Failed with the error message" + e[0]) + console.log("Second URL Failed with the error message" + e[1]) + console.log("Third URL Failed with the error message" + e[2]) + }) +``` + ### `.delay()` for future promises If you need a little bit of delay (such as retrying a method call to a service that is "eventually consistent") before doing something else, ``Q.delay()`` is your friend: diff --git a/kew.js b/kew.js index 101f390..814fc47 100644 --- a/kew.js +++ b/kew.js @@ -680,6 +680,60 @@ function allInternal(promises) { return promise } +/** + * Takes in an array of promises or literals and returns a promise that is + * fulfilled by the first given promise to be fulfilled, or rejected if all of the + * given promises are rejected. + * + * @param {!Array.} promises + * @return {!Promise} + */ +function any(promises) { + if (arguments.length != 1 || !Array.isArray(promises)) { + promises = Array.prototype.slice.call(arguments, 0) + } + return anyInternal(promises) +} + +/** + * A version of any() that does not accept var_args + * + * @param {!Array.} promises + * @return {!Promise} + */ +function anyInternal(promises) { + if (!promises.length) return resolve(null) + + var errorOutputs = [] + var output = [] + var finished = false + var promise = new Promise() + var counter = promises.length + + for (var i = 0; i < promises.length; i += 1) { + if(finished) break; + if (!promises[i] || !isPromiseLike(promises[i])) { + promise.resolve(promises[i]); + break; + } else { + promises[i].then(replaceEl.bind(null, output, 0)) + .then(function resolveFirstPromise() { + promise.resolve(output[0]); + finished = true; + }, function onAnyError(e) { + errorOutputs.push(e); + counter-- + if (!finished && counter === 0) { + finished = true + promise.reject(errorOutputs) + } + }) + } + } + + return promise +} + /** * Takes in an array of promises or values and returns a promise that is * fulfilled with an array of state objects when all have resolved or @@ -828,6 +882,7 @@ function bindPromise(fn, scope, var_args) { module.exports = { all: all + , any: any , bindPromise: bindPromise , defer: defer , delay: delay diff --git a/test/static.js b/test/static.js index ff5c86a..6aea121 100644 --- a/test/static.js +++ b/test/static.js @@ -201,6 +201,157 @@ exports.testAllIsPromiseLike = function(test) { }) } +// test Q.any with an empty array +exports.testQAnyEmptySuccess = function (test) { + var promises = [] + + // make sure nothing comes back + Q.any(promises) + .then(function (data) { + test.equal(data, null, "No records should be returned") + test.done() + }) +} + +// test Q.any with only literals +exports.testQAnyLiteralsSuccess = function (test) { + var vals = [3, 2, 1] + var promises = [] + + promises.push(vals[0]) + promises.push(vals[1]) + promises.push(vals[2]) + + // make sure only one result comes back + Q.any(promises) + .then(function (data) { + test.notEqual(vals.indexOf(data), -1, "The first promise to resolve should be returned") + test.done() + }) +} + +// test Q.any with only promises +exports.testQAnyPromisesSuccess = function (test) { + var vals = [3, 2, 1] + var promises = [] + + promises.push(Q.resolve(vals[0])) + promises.push(Q.resolve(vals[1])) + promises.push(Q.resolve(vals[2])) + + // make sure only one result comes back + Q.any(promises) + .then(function (data) { + test.notEqual(vals.indexOf(data), -1, "The first promise to resolve should be returned") + test.done() + }) +} + +// create a promise which waits for other promises +exports.testQAnyAssortedSuccess = function (test) { + var vals = [3, 2, 1] + var promises = [] + + // a promise that returns the value immediately + promises.push(Q.resolve(vals[0])) + + // the value itself + promises.push(vals[1]) + + // a promise which returns in 10ms + var defer = Q.defer() + promises.push(defer.promise) + setTimeout(function () { + defer.resolve(vals[2]) + }, 10) + + // make sure only one result comes back + Q.any(promises) + .then(function (data) { + test.notEqual(vals.indexOf(data), -1, "The first promise to resolve should be returned") + test.done() + }) +} + +// test Q.any with a failing promise +exports.testQAnyError = function (test) { + var vals = [3, 2, 1] + var err = new Error("hello") + var promises = [] + + var defer = Q.defer() + promises.push(defer.promise) + defer.reject(err) + + promises.push(vals[0]) + promises.push(vals[1]) + + // make sure only one result comes back + Q.any(promises) + .then(function (data) { + test.notEqual(vals.indexOf(data), -1, "The first promise to resolve should be returned") + test.done() + }) +} + +// test Q.any with all failing promises +exports.testQAnyOnlyError = function (test) { + var err1 = new Error("hello") + var err2 = new Error("hola") + var promises = [] + + var defer = Q.defer() + promises.push(defer.promise) + defer.reject(err1) + + var anotherDefer = Q.defer() + promises.push(anotherDefer.promise) + anotherDefer.reject(err2) + + var errArray = [err1, err2]; + + // All errors are returned in an array + Q.any(promises) + .fail(function (e) { + test.equal(e[0], err1) + test.equal(e[1], err2) + test.done() + }) +} + +// test any var_args +exports.testAnyVarArgs = function (test) { + var promises = ['a', 'b'] + + Q.any.apply(Q, promises) + .then(function (result) { + test.notEqual(promises.indexOf(result), -1, "The first promise to resolve should be returned") + test.done() + }) +} + +// test any array +exports.testAnyArray = function (test) { + var promises = ['a', 'b'] + + Q.any(promises) + .then(function (result) { + test.notEqual(promises.indexOf(result), -1, "The first promise to resolve should be returned") + test.done() + }) +} + +exports.testAnyIsPromiseLike = function(test) { + var promises = [originalQ('b'), 'a'] + var promisesResultTest = ['a', 'b']; + + Q.any(promises) + .then(function (result) { + test.notEqual(promisesResultTest.indexOf(result), -1, "The first promise to resolve should be returned") + test.done() + }) +} + // test delay exports.testDelay = function (test) { var val = "Hello, there"