JavaScript Promises

Creating Promises

Wrap async operations
const delay = (ms) => new Promise((resolve) => {
    setTimeout(() => resolve(`waited ${ms}ms`), ms);
});

const fetchData = (url) => new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.onload = () => resolve(xhr.responseText);
    xhr.onerror = () => reject(new Error(`Failed to fetch ${url}`));
    xhr.send();
});

A Promise is a container for a future value. resolve signals success, reject signals failure. The executor function runs immediately.

Consuming Promises

then, catch, finally
delay(1000)
    .then(msg => {
        console.log(msg);      // "waited 1000ms"
        return "next value";
    })
    .then(val => console.log(val))  // "next value"
    .catch(err => console.error(err))
    .finally(() => console.log("done"));

.then() chains transformations. Each .then() returns a new Promise, enabling chaining. .catch() handles any error in the chain. .finally() runs regardless of success or failure.

Promise Combinators

Coordinate multiple promises
const urls = [
    "https://api.example.com/hosts",
    "https://api.example.com/ports",
    "https://api.example.com/vlans",
];

// Promise.all — wait for ALL, fail on first error
const results = await Promise.all(urls.map(u => fetch(u)));
// All succeeded — results is an array of responses

// Promise.allSettled — wait for ALL, never reject
const outcomes = await Promise.allSettled(urls.map(u => fetch(u)));
outcomes.forEach(({ status, value, reason }) => {
    if (status === "fulfilled") console.log("OK:", value.status);
    else console.error("FAIL:", reason);
});

// Promise.race — first to settle (resolve OR reject)
const fastest = await Promise.race([
    fetch("https://api1.example.com/data"),
    fetch("https://api2.example.com/data"),
]);

// Promise.any — first to RESOLVE (ignores rejections)
const first = await Promise.any([
    fetch("https://primary.example.com"),
    fetch("https://fallback.example.com"),
]);

Promise.all fails fast on the first rejection. Promise.allSettled gives you the result of every promise regardless. Choose based on whether partial failure is acceptable.

Error Handling

Rejection propagates through the chain
fetch("/api/hosts")
    .then(res => {
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        return res.json();
    })
    .then(data => processData(data))
    .catch(err => {
        // catches network errors, HTTP errors, and processData errors
        console.error("Pipeline failed:", err.message);
    });

A .catch() at the end of a chain handles errors from any step above it. Unhandled promise rejections used to be silent — modern Node.js and browsers treat them as crashes.

Creating Resolved/Rejected Promises

Static factory methods
// Already resolved
const cached = Promise.resolve({ name: "sw01", ip: "10.50.1.10" });

// Already rejected
const failed = Promise.reject(new Error("not implemented"));

// Useful for consistent return types
function getHost(name) {
    const cache = hosts.get(name);
    if (cache) return Promise.resolve(cache);
    return fetchHost(name);  // returns a Promise
}

Promise.resolve and Promise.reject wrap synchronous values in a Promise. This lets you return a consistent type from functions that may or may not need async work.