23 May 2024

practical-async-javaScript image

Practical Async JavaScript

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! 👋