Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added .any() to kew, with tests and docs #51

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ logs
results

node_modules
npm-debug.log
npm-debug.log

.idea/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for what it's worth, i find it helpful to create a global .gitignore, rather than adding my IDE-specific ignores to every project I contribute to
https://help.github.com/articles/ignoring-files/#create-a-global-gitignore

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had no clue about this, thank you for sharing. Should I revert the gitignore to its last commit's state?

32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
55 changes: 55 additions & 0 deletions kew.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.<!Promise>} 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.<!Promise>} 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]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think you need a guard here if the promise has already been resolved?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, sorry, but I didn't understand that.
When a promise resolves, my "finished" becomes true and i break out of loop.

What do you mean by guard and where? I took cue (read copied) from the allInternal function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you have a race condition. resolveFirstPromise happens asynchronously,
after the loop has finished.

On Wed, Jul 15, 2015 at 1:47 PM, Ketan Bhatt [email protected]
wrote:

In kew.js #51 (comment):

  • 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]);
    

Hi, sorry, but I didn't understand that.
When a promise resolves, my "finished" becomes true and i break out of
loop.

What do you mean by guard and where? I took cue (read copied) from the
allInternal function.


Reply to this email directly or view it on GitHub
https://github.com/Medium/kew/pull/51/files#r34707149.

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
Expand Down Expand Up @@ -828,6 +882,7 @@ function bindPromise(fn, scope, var_args) {

module.exports = {
all: all
, any: any
, bindPromise: bindPromise
, defer: defer
, delay: delay
Expand Down
151 changes: 151 additions & 0 deletions test/static.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down