Introduction
Let's take a moment to brush up on some JavaScript async concepts together.
What is Asynchronous?
First off, let's remind ourselves what an asynchronous process is. Simply put, it's a process that doesn't execute immediately but has a delay or lag. It's like ordering a coffee at a busy café - you place your order, and it takes a bit of time before you get your cup.
Creating a Fake API Call
Alright, can we create a function that mimics a basic API call?
Absolutely! Here's a simple example:
function fakeApiCall(response) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`Success: ${response}`);
}, 1000);
});
}
Our fakeApiCall function takes a value and returns it after a 1-second delay, labeled with "Success." We're using a Promise because it always handles asynchronous code, and setTimeout to manage the delay.
Adding Error Handling
But hey, do API calls always return the expected value? Nope, sometimes they return errors. Let's add this functionality too:
function fakeApiCall(response) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve(`Success: ${response}`);
} else {
reject('API call failed: try again');
}
}, 1000);
});
}
Here, we've added a condition using Math.random(). Now, there's a 50% chance of success and a 50% chance of failure, mimicking real-world API behavior.
Retrying on Failure
Great! But what if we want to retry the API call when it fails? Let's tackle that:
let attempt = 0;
async function makeApiCallWithRetries(value) {
try {
const result = await fakeApiCall(value);
return result;
} catch (error) {
attempt++;
console.log(`Attempt ${attempt} failed: ${error} - for value: ${value}`);
return makeApiCallWithRetries(value);
}
}
In this code, we retry until we get a successful response.
Note, this could lead to an infinite loop if we're unlucky, but we'll hope for the best!
Setting a Retry Limit
To avoid that infinite loop, let's set a maximum retry limit:
let attempt = 0;
const maxRetries = 5;
async function makeApiCallWithRetries(value) {
try {
const result = await fakeApiCall(value);
return result;
} catch (error) {
attempt++;
if (attempt >= maxRetries) {
throw new Error(`Max retries reached: ${error}`);
}
console.log(`Attempt ${attempt} failed: ${error} - for value: ${value}`);
return makeApiCallWithRetries(value);
}
}
Now, we'll stop trying after five attempts, avoiding the dreaded infinite loop.
Making Parallel API Calls
So, what if we need to make multiple API calls simultaneously?
Let's think this through. Imagine we have a bunch of requests that need to go out all at once. How can we handle that efficiently?
Well, we can run them in parallel. Here's how:
async function makeApiCallsInParallel(values) {
const promises = values.map((value) => makeApiCallWithRetries(value));
const results = await Promise.allSettled(promises);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Response ${index + 1} succeeded: ${result.value}`);
} else {
console.log(`Response ${index + 1} failed: ${result.reason}`);
}
});
}
This function takes an array of values and calls makeApiCallWithRetries for each value in parallel. We're using Promise.allSettled to handle both successful and failed calls.
Conclusion
I hope this refreshed your memory about core JavaScript async functionality or even taught you something new.
Thanks for hanging out! Hit subscribe for more! 👋
Top comments (17)
Nice article! I always enjoy seeing articles about promises and async JavaScript!
On the "retries" logic, though, you've created a single, global attempt counter. I would suggest a slight change to passing the values into the function. This keeps it isolated in scope and also uses good patterns when dealing with recursion. Something like this, perhaps:
Using default values allows them to be auto-populated on the first call and then incremented only within the recursive call, and it allows us to specify different maxRetries for different APIs, in case some are very flaky.
Yes, that's a great suggestion. In production code, I would definitely implement it as you demonstrated, with a default value
Thanks!
this is a great article. however, there is an opinion in the dev community that we should not be using async/await as it blocks the thread. Instead we should use observables. Care to shed a light on this?
Async/await is non-blocking. They are literally a different way of writing promises, and if it help you can think of Promises as roughly equivalent to observables that are guaranteed to will only ever produce one event. They cover different use cases, but they are async.
Observables may be used to initiate promises, e.g. the classic RxJS Wikipedia search demo from MIX11, but the promise is still non-blocking
I would appreciate it if you have any links to information on this topic, in case I am misunderstanding or missing context.
Using await is what blocks the thread. I think that's what he meant
Seconding what @oculus42 said: unless there's a misunderstanding or missing context, this is still untrue.
Await is not blocking. It defers execution of subsequent code until after the awaited code completes, but it doesn't block the thread.
Very useful!
I love the idea of using Math.random to simulate success and error states. I often have to prototype UI before an API is ready and this works better than having to write two functions and comment out the state I don't want.
Insightful
Insightful
Thanks that help me to understand async use in React.
Hi Bogdan Varlamov,
Thanks for sharing
Clean and concise ✌️