Asynchronous programming is at the heart of modern JavaScript, and understanding Promises is the key to writing code that handles network requests, file reads, and timers without blocking the browser. A Promise represents a value that may not be available yet — it is either pending, fulfilled, or rejected. Before Promises, developers used nested callbacks that produced deeply indented, hard-to-read code nicknamed “callback hell.” Promises flatten that structure by chaining .then() calls, and async/await syntax — introduced in ES2017 — makes asynchronous code look almost identical to synchronous code while still being non-blocking. The async keyword before a function declaration makes it return a Promise automatically; await inside that function pauses execution until the awaited Promise settles. Error handling uses the familiar try/catch syntax rather than .catch() chains, which most developers find easier to read. Promise.all() lets you run multiple asynchronous operations in parallel and wait for all of them to complete, which is far faster than running them sequentially. This foundation is essential for working with the Fetch API, which powers the asynchronous data loading in WordPress themes, and for the debounced search inputs that fire API calls on a delay. All examples below use vanilla JavaScript and work in all modern browsers without a polyfill.
Problem: You need to perform asynchronous operations in JavaScript (API calls, timers, parallel requests) and want to avoid callback hell while handling errors cleanly.
Solution: Use Promises and async/await as shown in the examples below:
// 1. Basic Promise
const myPromise = new Promise( (resolve, reject) => {
const success = true;
if (success) {
resolve('Data loaded');
} else {
reject(new Error('Something went wrong'));
}
} );
myPromise
.then( result => console.log(result) ) // 'Data loaded'
.catch( err => console.error(err) );
// 2. Fetch with .then() chain
fetch('/wp-json/wp/v2/posts?per_page=3')
.then( response => {
if ( ! response.ok ) throw new Error('Network response was not ok');
return response.json();
} )
.then( posts => posts.forEach( p => console.log(p.title.rendered) ) )
.catch( err => console.error('Fetch failed:', err) );
// 3. async/await — same request, cleaner syntax
async function loadPosts() {
try {
const response = await fetch('/wp-json/wp/v2/posts?per_page=3');
if ( ! response.ok ) throw new Error('HTTP error: ' + response.status);
const posts = await response.json();
posts.forEach( p => console.log(p.title.rendered) );
} catch (err) {
console.error('Failed to load posts:', err);
}
}
loadPosts();
// 4. Promise.all — run requests in parallel
async function loadParallel() {
const [posts, pages] = await Promise.all([
fetch('/wp-json/wp/v2/posts').then(r => r.json()),
fetch('/wp-json/wp/v2/pages').then(r => r.json()),
]);
console.log('Posts:', posts.length, '| Pages:', pages.length);
}
loadParallel();
NOTE: await can only be used inside an async function — using it at the top level of a regular script will throw a syntax error in older environments (though top-level await is now supported in ES modules). When you need to run async operations inside a forEach loop and wait for all of them, use Promise.all( array.map( async item => ... ) ) instead of forEach with await, which does not actually wait for each iteration.