JavaScript Promises: A Practical Guide

A Promise is a JavaScript object representing the eventual completion or failure of an asynchronous operation. Introduced in ES6 (2015), Promises replaced callback-heavy code with a cleaner chaining syntax, and they’re the foundation of async/await.

Problem: How do you handle asynchronous JavaScript operations — API calls, timers, or file reads — without falling into deeply nested callback functions?

Solution: Use Promises to represent the eventual result of an async operation. Chain .then() for success and .catch() for errors; use Promise.all() to run multiple operations in parallel and wait for all of them to resolve.

// Creating a Promise
const wait = ( ms ) => new Promise( ( resolve, reject ) => {
    if ( ms < 0 ) {
        reject( new Error( 'Wait time cannot be negative' ) );
        return;
    }
    setTimeout( resolve, ms );
} );

// Consuming a Promise — .then() / .catch() / .finally()
wait( 1000 )
    .then( () => console.log( 'Done after 1 second' ) )
    .catch( err => console.error( err.message ) )
    .finally( () => console.log( 'Always runs' ) );

Chaining — each .then() receives the return value of the previous one:

fetch( '/wp-json/wp/v2/posts?per_page=5' )
    .then( response => {
        if ( ! response.ok ) throw new Error( `HTTP error: ${response.status}` );
        return response.json();   // returns another Promise
    } )
    .then( posts => posts.map( p => p.title.rendered ) )
    .then( titles => console.log( titles ) )
    .catch( err => console.error( 'Fetch failed:', err ) );

Static Promise methods:

// Promise.all — wait for all, fail fast if any rejects
Promise.all( [
    fetch( '/wp-json/wp/v2/posts' ).then( r => r.json() ),
    fetch( '/wp-json/wp/v2/categories' ).then( r => r.json() ),
] ).then( ( [ posts, cats ] ) => {
    console.log( posts.length, cats.length );
} );

// Promise.allSettled — wait for all, never rejects (ES2020 but safe to use in 2019 with a polyfill)
// Promise.race — resolves/rejects as soon as the first Promise settles
Promise.race( [
    fetch( '/wp-json/wp/v2/posts' ),
    new Promise( ( _, reject ) => setTimeout( () => reject( new Error( 'Timeout' ) ), 5000 ) ),
] ).then( res => res.json() )
   .catch( err => console.error( err.message ) );

NOTE: Always attach a .catch() handler to every Promise chain — unhandled rejections crash the Node.js process (in newer versions) and produce console warnings in browsers. If you forget .catch(), use the global unhandledrejection event as a safety net: window.addEventListener('unhandledrejection', e => console.error(e.reason)).