Asynchronous Programming in JavaScript

Asynchronous programming in JavaScript: Promises, async/await

JavaScript is single-threaded but can handle asynchronous operations.
This prevents blocking execution during long operations.

Reminder: Synchronous vs Asynchronous

Synchronous (blocking) code

console.log("1. Start");
console.log("2. Processing");
console.log("3. End");

Asynchronous (non-blocking) code

console.log("1. Start");

setTimeout(() => {
    console.log("2. Asynchronous processing (in 1ms)");
}, 1);

console.log("3. End (displayed before async processing!)");

Callbacks (old style)

Callbacks were the first way to handle asynchronous operations.
Problem: "Callback Hell" with nesting

Simple callback example

function operationAsync(callback) {
    setTimeout(() => {
        const resultat = Math.random() > 0.5 ? "Success" : "Error";
        callback(resultat);
    }, 100);
}

operationAsync((resultat) => {
    console.log("Callback result:", resultat);
});

Callback Hell Problem

// Code difficult to read and maintain
function callbackHell() {
    setTimeout(() => {
        console.log("Step 1");
        setTimeout(() => {
            console.log("Step 2");
            setTimeout(() => {
                console.log("Step 3");
                // And so on...
            }, 100);
        }, 100);
    }, 100);
}

callbackHell();

Promises (ES6)

Promises solve the callback hell problem
Promise states: pending, fulfilled (resolved), rejected

Creating a Promise

function creerPromise(succes = true) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (succes) {
                resolve("Operation successful!");
            } else {
                reject(new Error("Operation failed!"));
            }
        }, 100);
    });
}

Usage with .then() and .catch()

creerPromise(true)
    .then(resultat => {
        console.log("Success:", resultat);
        return "Transformed data";
    })
    .then(donnees => {
        console.log("Transformation:", donnees);
    })
    .catch(erreur => {
        console.error("Error:", erreur.message);
    })
    .finally(() => {
        console.log("Cleanup (always executed)");
    });

Error handling

creerPromise(false)
    .then(resultat => {
        console.log("This line will not execute");
    })
    .catch(erreur => {
        console.error("Error captured:", erreur.message);
    });

Promise.all, Promise.race, etc.

Promise.all

// Promise.all: waits for ALL promises to complete
console.log("--- Promise.all ---");

const promiseA = creerPromise(true);
const promiseB = new Promise(resolve => setTimeout(() => resolve("B"), 150));
const promiseC = new Promise(resolve => setTimeout(() => resolve("C"), 200));

Promise.all([promiseA, promiseB, promiseC])
    .then(resultats => {
        console.log("All results:", resultats); // ["Operation successful!", "B", "C"]
    })
    .catch(erreur => {
        console.error("One of the promises failed:", erreur);
    });

Promise.race

// Promise.race: returns the result of the FIRST promise that completes
console.log("--- Promise.race ---");

const promiseLente = new Promise(resolve => setTimeout(() => resolve("Slow"), 300));
const promiseRapide = new Promise(resolve => setTimeout(() => resolve("Fast"), 100));

Promise.race([promiseLente, promiseRapide])
    .then(resultat => {
        console.log("First result:", resultat); // "Fast"
    });

Promise.allSettled

// Promise.allSettled: waits for all promises, even in case of error
console.log("--- Promise.allSettled ---");

const promiseSuccess = Promise.resolve("Success");
const promiseError = Promise.reject(new Error("Error"));

Promise.allSettled([promiseSuccess, promiseError])
    .then(resultats => {
        console.log("All results (settled):", resultats);
        // [{ status: "fulfilled", value: "Success" }, { status: "rejected", reason: Error }]
    });

Async/Await (ES2017)

async/await makes asynchronous code more readable
An async function always returns a Promise

Basic async function

async function fonctionAsync() {
    try {
        console.log("Start of async function");

        const resultat1 = await creerPromise(true);
        console.log("Result 1:", resultat1);

        const resultat2 = await new Promise(resolve =>
            setTimeout(() => resolve("Second result"), 100)
        );
        console.log("Result 2:", resultat2);

        return "Function completed";
    } catch (erreur) {
        console.error("Error in async function:", erreur.message);
        throw erreur; // Re-throw the error
    }
}

// Calling an async function
fonctionAsync()
    .then(resultat => console.log("Return of the function:", resultat))
    .catch(erreur => console.error("Final error:", erreur.message));

Comparison Promises vs async/await

With Promises (chaining)

function avecPromises() {
    return creerPromise(true)
        .then(resultat1 => {
            console.log("Promise - Result 1:", resultat1);
            return creerPromise(true);
        })
        .then(resultat2 => {
            console.log("Promise - Result 2:", resultat2);
            return "Completed with Promises";
        });
}

With async/await (more readable)

async function avecAsyncAwait() {
    const resultat1 = await creerPromise(true);
    console.log("Async - Result 1:", resultat1);

    const resultat2 = await creerPromise(true);
    console.log("Async - Result 2:", resultat2);

    return "Completed with async/await";
}