Beat the Clock - Using Promise.race to Optimize App Performance

Published on

Imagine you’re at the grocery store, eyeing the checkout lines. One has a single shopper with a mountain of groceries, another has three people with just a few items each. Which line do you pick? The goal is simple: get out of the store as quickly as possible. In coding, when dealing with redundant or backup systems, the challenge is much the same: how to get the fastest response without being stuck waiting for the slowpoke.

This post is part of the naive.dev series on Promise.race. If you’re new to Promise.race, or if you’d like a refresher, check out the introductory post which covers what Promise.race is, how it works, and a list of potential use cases. Each use case in the series is linked on the original post as new posts go live.

Today’s focus: leveraging Promise.race to always rely on the fastest response from redundant or backup systems.


The Problem

Picture this: You’ve built a weather app. It pulls data from multiple sources like “FastWeather” and “ReliableWeather” to ensure uptime and accuracy. But what happens when one source is delayed due to network issues, leaving your app—and your users—waiting? Nobody wants to wait for the slowest source when faster options are available. That’s where redundancy meets optimization.

The solution? Use Promise.race to let your app act like the savvy shopper who always picks the fastest checkout line.


A Quick Refresher on Promise.race

Promise.race is a JavaScript method that takes an array of promises and returns a promise that resolves or rejects as soon as any of the input promises settle. It’s the equivalent of a race: the first to cross the finish line wins.

For a deeper dive into the mechanics of Promise.race, checkout the first post of this series. Today, let’s focus on how to apply it to a practical use case: handling redundant systems for the quickest response.


Real-World Use Case: Weather APIs

Let’s go back to the weather app example. Suppose you’re fetching temperature data from two APIs:

  • FastWeather: Known for speed but occasionally returns errors.
  • ReliableWeather: Slower but rarely fails.

The goal? Serve the fastest accurate response. Here’s how you can achieve that using Promise.race:

const fetchFromFastWeather = () =>
	fetch('https://api.fastweather.com/current').then((res) => res.json());

const fetchFromReliableWeather = () =>
	fetch('https://api.reliableweather.com/current').then((res) => res.json());

const getFastestWeather = async () => {
	try {
		const result = await Promise.race([fetchFromFastWeather(), fetchFromReliableWeather()]);

		// Perform validation and other stuff required related to the data

		return result;
	} catch (error) {
		// Log error or handle
		// I'd recommend adding fallback logic here
		return { temperature: 'Unavailable', source: 'Fallback' };
	}
};

getFastestWeather().then((data) => console.log('Weather data:', data));

This approach ensures that the user always gets the quickest valid response. If one API is slow or fails entirely, the other can step in seamlessly.


Practical Considerations

1. Data Validation

Always validate the response from the fastest source. A quick response isn’t helpful if the data is incomplete or incorrect.

2. Error Handling

When racing promises, it’s possible for all of them to fail. Incorporating fallback logic ensures that your app doesn’t crash or leave users in the dark. Remember, Promise.all returns the fastest response (even if the fastest is a failure). As such, having fallbacks is highly recommended when using it.

3. Consistency Across Sources

When working with multiple APIs, ensure that their data formats are compatible. This avoids additional overhead in converting or handling mismatched data.

4. Throttling and Rate Limits

Fetching data from multiple sources simultaneously can hit API rate limits. Implement throttling or caching to mitigate this.


Wrapping Up

By using Promise.race, redundant systems become an asset rather than a liability. Your app delivers data faster, keeps users happier, and avoids the headache of slow or unresponsive systems. Remember, speed isn’t just about algorithms—it’s about smart choices, like picking the fastest checkout line or relying on the quickest API.

If this use case inspired you, explore others in the naive.dev series on Promise.race. And as always, code like a sprinter, not a marathoner. Why wait when you can race?


Stay tuned for more posts in this series. Got a specific use case you’d like to see covered? Drop a comment or reach out!

© 2017 - 2025 / John Gicharu - Gicharu Solutions