Dealing with Slow-Loading URLs Like a Pro!

Published on

Ever had your site stuck loading because one script is taking forever to show up? It’s like waiting for that one teammate who’s always late to meetings (you know the one). As developers, we’ve all been there—staring at our monitors, waiting for something to load, questioning all of our life choices. But fear not! JavaScript’s Promise.race is here to save the day. In this post, we’ll explore how you can use it to dynamically load scripts or stylesheets from multiple URLs, letting the fastest one win—because who has time to wait for the slowpoke script, right?

By the way, this post is part of a series exploring practical uses of Promise.race. If you haven’t yet, check out the previous entries for a deeper dive into its capabilities and quirks.

The Problem with Single Source Loading

Relying on a single URL for critical resources is like relying on one friend to pick you up from the airport—great if they show up, but you’re stranded if they don’t. Servers go down, networks act up, and suddenly your users are stuck waiting. To avoid this, it’s good practice to have fallback URLs for essential resources, ensuring that at least one will come through.

But how do we pick the fastest one without manually trying each? That’s where Promise.race comes in.

Wait, What is Promise.race?

Think of Promise.race as a literal race: you’ve got multiple competitors (promises), and the first one to finish—whether it’s a success or failure—is the winner. In case you missed it, we explained Promise.race in detail in our first post of the series, so check that out if you’re looking for a thorough breakdown.

In essence:

Promise.race([promise1, promise2, promise3])
	.then((winner) => console.log('Winner:', winner))
	.catch((error) => console.error('Error:', error));

The first promise to settle (resolve or reject) determines the outcome. As for the use cases, let’s just say they are many. However, if you are curious, here’s a bunch of them on the first post of this series.

Dynamic Loading In Action

Imagine you need to load a script—it’s a critical library your app depends on. You have multiple URLs to choose from, but instead of picking one, you decide to race them. Here’s how:

function loadScript(url) {
	return new Promise((resolve, reject) => {
		const script = document.createElement('script');
		script.src = url;
		script.onload = () => resolve(`Loaded: ${url}`);
		script.onerror = () => {
			// You can check for the script first before trying to remove
			// To ensure you cover all bases and since it can reject with an err
			// However, it makes no difference as it has failed in any case
			// and we are rejecting in the next line anyway
			document.head.removeChild(script);
			reject(new Error(`Failed to load: ${url}`));
		};
		document.head.appendChild(script);
	});
}

const urls = [
	'https://cdn1.example.com/library.js',
	'https://cdn2.example.com/library.js',
	'https://cdn3.example.com/library.js'
];

Promise.race(urls.map(loadScript))
	.then((result) => console.log(result))
	.catch((err) => console.error(err));

The urls array above contains a list of URLs that all resolve with the same library. However, we aren’t sure which one will be the fastest to resolve for the user. As such, we race them and whichever resolves the fastest, we resolve the promise and essentially cancel the loading of the others. In this regard, the browser essentially says, “whoever is ready first, let me know!” And voila, your app gets what it needs ASAP or rather, ASA (as soon as it is available).

When to Use This Approach

In as far as loading assets dynamically, Promise.race is perfect if;

  • One is loading fallback stylesheets, scripts, or other assets.
  • You need to fetch data fast, have multiple endpoints and are unsure which one to use.
  • A good percentage of your users tend to use unreliable networks (hello, public WiFi).

Remember: Promise.race is only relevant if the resources you are trying to load are interchangeable - you wouldn’t want to load D3.js instead of JQuery by mistake! 😉

Caveats and Gotchas

Before you start setting up your own races, there are two potential caveats, or rather gotchas related to this method;

  • Fastest isn't always the best: The first URL might respond pretty quickly but fail halfway while loading. Remember, this method checks for which resource responds the fastest. As such, I’d recommend making sure your scripts are reliable before implementing this approach.
  • All Might/Can Fail: In some cases, all your promises might reject, and if they all do, Promise.race will too. In this case, you’ll need a backup plan. In some cases, a retry logic or having a default fallback might just be what you need. In a future post, I’ll be writing about handling fallbacks and how to handle them effectivel so stay tuned! 😉

Conclusion

Promise.race is a powerful tool for dynamic loading, helping you deliver resources quickly without being held hostage by slow servers. Whether it’s scripts, stylesheets, or API calls, this approach ensures your app stays responsive and your users stay happy.

So, go ahead, try it out in your next project. And stay tuned for more posts in this series where we dive deeper into practical uses of Promise.race. Got a creative use case? Let me know—I might just write about it!

Happy coding!

© 2017 - 2025 / John Gicharu - Gicharu Solutions