JavaScript Mapping Library
Let me begin by saying that "Promise Collection Methods" is not something I’ve seen mentioned elsewhere, but is my own way of referring to the various methods of the Promise API that work with multiple promises. They are:
I’ve used Promise.all many times in the past, and I was aware of the other methods but had not taken the time to actually build a demo of them. This weekend I changed that. After spending a few hours in Sanctuary grinding my Necro character, I put down the controller and picked up the laptop. Here’s what I built. As a note, everything shown here works in modern browsers, but you can check MDN for more information on compatibility if you need.
Promise.all
Before getting into the important code, I built some methods to help me test things out, help me display stuff, and so forth. First, I knew I was going to build this on CodePen (I’ll be embedding it below) and I wanted something would visually display in the browser, so I added this HTML:
<div id="log"><pre></pre></div>
And wrote this little utility:
const log = s => { document.querySelector('#log pre').innerHTML += s + '\n' };
This lets me then use log("like, whatever") in my code and have it rendered out to the browser instead of the developer console.
log("like, whatever")
Next, a function to handle returning promises with different times and optionally in an error state:
const makePromise = (name, secs, fail=false) => { return new Promise((resolve, reject) => { setTimeout(() => { if(!fail) { log(`Good result for $ {name}`); resolve(`Resolve from $ {name}`); } else { log(`Bad result for $ {name}`); reject(`Fail from $ {name}`); } }, secs * 100); });}
Here’s a few examples of it in use:
makePromise('Todd', 3);makePromise('Scott', 1);
This will create two promises. One resolves in three seconds, the other in 1. Next:
makePromise('Ray', 2, true);
This will reject as an error in two seconds.
Also, note that I use the log method here so I can see stuff resolving in real-time. This will become important later.
log
Finally, I wrote a simple delay function:
const delay = x => { return new Promise(resolve => setTimeout(resolve, x*1000));}
This will let me stuff like:
delay(4);
As a quick and hacky way to delay a few seconds. In theory, I could have made makePromise make use of it, but I didn’t bother updating it. Alright, let’s get started.
makePromise
The all method has the following behavior:
all
Given an array of Promises, resolve when all are done, or immediately when any throw an error.
It resolves with an array of results that match the results of inputs. Here’s my first test:
log('TEST ONE - Promise.all(all good)');let test = [ makePromise('alpha', 3), makePromise('bob', 1), makePromise('charly', 3)];results = await Promise.all(test); log('Results from test with Promise.all');log(JSON.stringify(results));
Looking at the code, you can see that bob will return before alpha and charly. Here’s the output:
bob
alpha
charly
Good result for bobGood result for alphaGood result for charlyResults from test with Promise.all["Resolve from alpha","Resolve from bob","Resolve from charly"]
Notice that even though the order was different than the input, the results match the input which is great.
Now let’s throw an error into the mix:
log('\n\nTEST TWO - Promise.all (one bad)'); test = [ makePromise('alpha', 3), makePromise('failwhale', 1, true), makePromise('charly', 3)];try { let results = await Promise.all(test);} catch(e) { log(`Expected failure in test: $ {e}`);}
I wrapped the call in a try/catch to handle the rejection. Here’s the output:
try/catch
TEST TWO - Promise.all (one bad)Bad result for failwhaleExpected failure in test: Fail from failwhaleGood result for alphaGood result for charly
Notice that my handler fires as soon as the error occurs, but the other promises are still running. You don’t have to use try/catch. Since the result of Promise.all is itself a promise, you can use the catch method of Promise instead:
catch
log('\n\nTEST TWO A - Promise.all (one bad), modified handler.'); Promise.all(test).then(results => log('all good')).catch(e => log('one bad'));
This returns, as you would expect, just one bad.
one bad
While Promise.all is good for when you’re pretty sure everything is going to work out ok (and remember, a Fetch call to an API may run just fine, but the API itself may return with an error), you may find [allSettled] method a bit more flexible. It’s behavior is:
allSettled
Given an array of Promises, resolve when all are done and report on the success or failure of each Promise.
This means you can now rely on knowing when everything is done and handle the success or failure of each one by one. Here’s an example:
log('\n\nTEST THREE - Promise.allSettled (all good)');test = [ makePromise('alpha', 3), makePromise('bob', 1), makePromise('charly', 3)];results = await Promise.allSettled(test); log('Results from test with Promise.allSettled');log(JSON.stringify(results,null,'\t'));
The result now is a bit different:
[ { "status": "fulfilled", "value": "Resolve from alpha" }, { "status": "fulfilled", "value": "Resolve from bob" }, { "status": "fulfilled", "value": "Resolve from charly" }]
Now we get both the value from the resolve as well as a status flag. Here’s an example with a failure. First the calls:
resolve
status
log('\n\nTEST FOUR - Promise.allSettled (one bad)');test = [ makePromise('alpha', 3), makePromise('failwhale', 1, true), makePromise('charly', 3)];results = await Promise.allSettled(test); log('Results from test with Promise.allSettled');log(JSON.stringify(results,null,'\t'));
And then the result:
[ { "status": "fulfilled", "value": "Resolve from alpha" }, { "status": "rejected", "reason": "Fail from failwhale" }, { "status": "fulfilled", "value": "Resolve from charly" }]
You can see the failure in the second result. Woot. I think this is my favorite so far. Going on…
The any method works like so:
any
Given an array of Promises, resolve when any of the Promises resolves, or reject if all of them fail.
This one’s kind of interesting. It’s basically the "try really hard for something to work" collection method. Here’s a first example:
log('\n\nTEST FIVE - Promise.any(all good)');test = [ makePromise('alpha', 3), makePromise('bob', 1), makePromise('charly', 3)];results = await Promise.any(test); log('Results from test with Promise.any');log(results);
In this one, bob is the winner:
TEST FIVE - Promise.any(all good)Good result for bobResults from test with Promise.anyResolve from bobGood result for alphaGood result for charly
Next, here’s one with an error. It’s the quickest, but any keeps trying:
log('\n\nTEST SIX - Promise.any(one bad)');test = [ makePromise('alpha', 3), makePromise('bad bob', 1, true), makePromise('charly', 3)];results = await Promise.any(test); log('Results from test with Promise.any');log(results);
And the output:
TEST SIX - Promise.any(one bad)Bad result for bad bobGood result for alphaResults from test with Promise.anyResolve from alphaGood result for charly
Finally, here’s one where they all fail.
log('\n\nTEST SEVEN - Promise.any(all bad)');test = [ makePromise('alpha', 3, true), makePromise('bad bob', 1, true), makePromise('charly', 3, true)];try { let results = await Promise.any(test); log(results);} catch(e) { log(`Expected failure in test: $ {e}`); log(e.errors);}
Notice I log e.errors – this is an additional value thrown by the method that contains an array of all the messages from the failed promises. (It’s an AggregateError).
e.errors
Here’s the output:
TEST SEVEN - Promise.any(all bad)Bad result for bad bobBad result for alphaBad result for charlyExpected failure in test: AggregateError: All promises were rejectedFail from alpha,Fail from bad bob,Fail from charly
For the final method I’ll cover, race has this behavior:
race
Given an array of promises, resolve or reject with whatever happens first.
Here are a few examples. First, all good:
log('\n\nTEST RAY EIGHT - Promise.race(all good)');test = [ makePromise('alpha', 3), makePromise('bob', 1), makePromise('charly', 3)];results = await Promise.race(test); log('Results from test with Promise.race');log(results);
TEST RAY EIGHT - Promise.race(all good)Good result for bobResults from test with Promise.raceResolve from bobGood result for alphaGood result for charly
And here’s one with a ‘bad’ winner:
log('\n\nTEST NINE - Promise.race(bad guy wins)');test = [ makePromise('alpha', 3), makePromise('worst bob', 1, true), makePromise('charly', 3)];try { let results = await Promise.race(test);} catch(e) { log(`Expected failure in test: $ {e}`);}
And its results:
TEST NINE - Promise.race(bad guy wins)Bad result for worst bobExpected failure in test: Fail from worst bobGood result for alphaGood result for charly
Pretty simple to understand I think. The MDN docs have a great example of how to use Promise.race to add timeouts to network calls.
Promise.race
You can see all of the above yourself at the following CodePen, but you may want to open it up in a new tab.
See the Pen Promise Collection Stuff by Raymond Camden (@cfjedimaster) on CodePen.
Raymond Camden
You must be logged in to post a comment.
This site uses Akismet to reduce spam. Learn how your comment data is processed.