⏳ javascript: async, promises & more

🤔 is javascript synchronous?

yes - javascript is a synchronous, single-threaded language, it runs one thing at a time, line by line.

but... we can mimic asynchronous behaviour using:

these tools allow us to:

🪝 callbacks

a callback is a function passed into another function to be run later.

function fetchData(callback) {
    setTimeout(() => {
        callback('data received!');
    },1000)
}

fetchData((result) => {
    console.log(result);
});

🧠 but callbacks can get messy - known as "callback hell":

doThing(() => {
    doNext() => {
        doAnother(() =>{
            ...
        })
    }
})

💭 promises

a promise is javascript object that represents a value you don't have yet, but will get in the future (or maybe not).

it's like saying "i promise you something later"

🧱 promise has 3 possible states:

you can .then() for success, .catch() for errors:

fetchData
    .then((res) => console.log(res))
    .catch((err) => console.log(err));

⚡ async / await

async function getData() {
    try {
        const res = await fetchData();
        console.log(res);
    } catch (err) {
        console.log(err);
    }
}

what does Async functions return and why?

⚙️ async await is a syntax sugar for promises and whenever you write async in front of a function, it will basically wrap that code in a promise and return a promise that will resolve with the return of that function.

🧠 so whenever you have an async function, that function will return a promise that will end up resolving with whatever the return of that function is.

example:

import {useEffect, useState } from "react";

function Characters() {
    const [people, setPeople ] = useState([]);

    async function getPeople(){
       try {
            const data = await fetch("https://swapi.dev/api/people");
            const response = await data.json();
            setPeople(response.results);
       }  catch(err) {
            console.log(err.message);
       }
    }

    useEffect(() => {
        getPeople();
    }, [])

    return <>
        {people.map((character, index) => (
            return 

{character.name}

)} } export default Characters;

🔄 Promise.all, Promise.race, Promise.allSettled

Promise.all

runs multiple promises in parallel, resolves when all succeed, rejects if any fail.

const [users, posts, comments] = await Promise.all([
  fetch("/api/users").then(r => r.json()),
  fetch("/api/posts").then(r => r.json()),
  fetch("/api/comments").then(r => r.json()),
]);

Promise.allSettled

waits for all to finish (success or failure).

const results = await Promise.allSettled([
  fetch("/api/users"),
  fetch("/api/failing-endpoint"),
]);
results.forEach(r => {
  if (r.status === "fulfilled") console.log(r.value);
  else console.log(r.reason);
});

Promise.race

resolves/rejects with the first promise to settle.


🔁 event loop basics

javascript is single-threaded but handles async via the event loop.

console.log("1"); // sync — runs first
setTimeout(() => console.log("2"), 0); // macro task — runs last
Promise.resolve().then(() => console.log("3")); // microtask — runs second
console.log("4"); // sync
// output: 1, 4, 3, 2

key rule: microtasks (promises) always run before macro tasks (setTimeout).


⏱️ handling API timeouts

async function fetchWithTimeout(url, timeoutMs = 5000) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
  try {
    const response = await fetch(url, { signal: controller.signal });
    clearTimeout(timeout);
    return await response.json();
  } catch (error) {
    clearTimeout(timeout);
    if (error.name === "AbortError") throw new Error("request timed out");
    throw error;
  }
}

🔁 retry logic

simple retry

async function fetchWithRetry(url, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      const res = await fetch(url);
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      return await res.json();
    } catch (error) {
      if (i === retries - 1) throw error;
    }
  }
}

retry with exponential backoff

async function fetchWithBackoff(url, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      const res = await fetch(url);
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      return await res.json();
    } catch (error) {
      if (i === retries - 1) throw error;
      const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s...
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

What is a pure function and when do we use it?

A pure function is very useful when using functional programming. Why? Because it really does not modify anything else — it has no side effects. 🌱

You really just give it an input, and it gives you an output, and there is no other change to:

This is extremely good because they are very deterministic ✅ — so you always know:

they're commonly used in functional programming and in most JavaScript codebases, because it makes things simpler, more predictable, and easier to test.