PWSHub News

Looking at the JavaScript Promise Collection Methods

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:

  • Promise.all
  • Promise.allSettled
  • Promise.any
  • Promise.race

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.

The Helper Functions #

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.

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.

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.

Promise.all #

The all method has the following behavior:

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:

Good result for bob
Good result for alpha
Good result for charly
Results 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:

TEST TWO  - Promise.all (one bad)
Bad result for failwhale
Expected failure in test: Fail from failwhale
Good result for alpha
Good 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:

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.

Promise.allSettled #

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:

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:

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...

Promise.any #

The any method works like so:

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 bob
Results from test with Promise.any
Resolve from bob
Good result for alpha
Good 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 bob
Good result for alpha
Results from test with Promise.any
Resolve from alpha
Good 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).

Here's the output:

TEST SEVEN - Promise.any(all bad)
Bad result for bad bob
Bad result for alpha
Bad result for charly
Expected failure in test: AggregateError: All promises were rejected
Fail from alpha,Fail from bad bob,Fail from charly

Promise.race #

For the final method I'll cover, race has this behavior:

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

And the output:

TEST RAY EIGHT - Promise.race(all good)
Good result for bob
Results from test with Promise.race
Resolve from bob
Good result for alpha
Good 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 bob
Expected failure in test: Fail from worst bob
Good result for alpha
Good 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.

Demo #

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.

Source: raymondcamden.com

Related stories
3 weeks ago - Are you ready to dive into the world of Python back-end development? Whether you’re a beginner looking to learn the basics or an experienced developer wanting to upgrade your skills, this handbook is your ultimate guide. Imagine being...
1 week ago - The Fetch API is a JavaScript function that you can use to send a request to any Web API URL and get a response. In this article, I'm going to show you how to make HTTP requests to external APIs using the JavaScript Fetch API. You're...
2 weeks ago - Many operations, such as network requests, are asynchronous in nature. One of the most useful and powerful tools for working with asynchronous code is the Promise. In this handbook, you'll learn all about JavaScript Promises and how to...
6 days ago - The Geolocation API is a standard API implemented in browsers to retrieve the location of the people who are interacting with a web application. This API enable users to send their location to a web application to enable relevant...
Other stories
2 hours ago - Here's a quick set of steps from my actual experiences of using Fedora Linux as daily driver, which you can follow.
7 hours ago - Written by Contributor Opinions expressed by Twilio contributors are their own In this post, we'll learn how to build an...
8 hours ago - In the world of data science, where raw information swirls in a cacophony of numbers and variables, lies the art of harmonizing data. Like a maestro conducting a symphony, the skilled data scientist orchestrates the disparate elements of...
9 hours ago - Windows 10 allows you to create a password lock on a folder so only you can access the content within. Here are the steps to set up a password-locked folder
9 hours ago - Want to mirror your iPhone screen on your Ubuntu desktop? There's a free, open-source app you can install from the Ubuntu repos to do exactly that. If
12 hours ago - Avid users of Varia, a modern download manager for Linux desktops, may want to check out official browser extensions. By installing the official Varia
13 hours ago - So far, all of my Alpine.js explorations have been client-side focused. But, my ultimate goal is to see if Alpine.js is a good companion framework for a ColdFusion-based multi-page application (MPA). As such, I wanted to spend some time...
13 hours ago - Mike Bostock, Announcing: Observable Framework: Today we’re launching Observable 2.0 with a bold new vision: an open-source static site generator for building fast, beautiful data apps, dashboards, and reports. Our …
14 hours ago - Windows has features to help SSDs operate to their full potential. Here is what you need to do if you have an SSD for your Windows PC.