Understanding JavaScript Promises: Concepts, Implementation, and Practical Use Cases
The article traces JavaScript promises from their 1988 origins and early libraries through the Promise/A+ spec and ES6 implementation, explains core rules, demonstrates practical rewrites using chaining, async/await, error handling, cancellation patterns, and parallel execution with Promise.all, offering guidance for robust asynchronous code.
Promise was first introduced in 1988 by Barbara Liskov and Liuba Shrira (see the paper "Promises: Linguistic Support for Efficient Asynchronous Procedure Calls in Distributed Systems"). Early implementations appeared in MultiLisp and Concurrent Prolog.
In JavaScript the concept became popular through jQuery’s jQuery.Deferred() and independent libraries such as Q, When and Bluebird. The lack of a unified API led to the Promise/A+ specification, which was later adopted by the ES6 (ES2015) standard.
Basic rules of a Promise
Provides a then() method that conforms to the spec.
Starts in the pending state and can transition to fulfilled or rejected .
Once settled, the state cannot change.
The settled value is immutable.
Example with Q:
// Q/2009-2017
import Q from 'q';
function wantOdd () {
const defer = Q.defer();
const num = Math.floor(Math.random() * 10);
if (num % 2) {
defer.resolve(num);
} else {
defer.reject(num);
}
return defer.promise;
}
wantOdd()
.then(num => { log(`Success: ${num} is odd.`); })
.catch(num => { log(`Fail: ${num} is not odd.`); });Native ES6 syntax:
new Promise((resolve, reject) => { /* executor */ })Using Promise to replace deeply‑nested callbacks (callback hell) makes the flow clearer. The article shows a real‑world login flow in a game project, originally written with callbacks, and rewrites it with async/await and Promise chaining, reducing the need to track intermediate callbacks.
Key practical rewrites:
Login process: const userInfo = await State.quickLogin();
Game page push: const [isSwitchOn, isLogined] = await Promise.all([this.getSwitchStatus(), this.getLoginStatus()]);
Async/Await
Async functions are syntactic sugar over Promises, allowing asynchronous code to look synchronous. They are non‑blocking at the JavaScript thread level but appear blocking within the function scope.
Advantages:
Less boilerplate compared to chaining .then() .
Easier to write conditional logic.
Example comparison:
// Promise version
function getUserInfo() {
return getData().then(data => data);
}
// Async/Await version
async function getUserInfo() {
return await getData();
}Error handling best practices:
Prefer .catch() at the end of a chain rather than providing a second argument to .then() .
Listen for unhandled rejections in browsers via window.addEventListener('unhandledrejection', …) and window.addEventListener('rejectionhandled', …) .
// Global unhandled rejection tracking
const unhandledRejections = new Map();
window.addEventListener('unhandledrejection', e => {
unhandledRejections.set(e.promise, e.reason);
});
window.addEventListener('rejectionhandled', e => {
unhandledRejections.delete(e.promise);
});
setInterval(() => {
unhandledRejections.forEach((reason, promise) => {
console.log('handle:', reason.message);
promise.catch(e => console.log('I catch u!', e.message));
});
unhandledRejections.clear();
}, 5000);Cancellation: native Promises lack a cancel() method. Common patterns include using Promise.race() with a timeout or third‑party libraries such as Speculation .
Promise.race([anAsyncFn(), timeout(5000)])Iterators and reduction can be used to execute asynchronous tasks sequentially:
function wasteTime(ms) {
return new Promise(resolve => setTimeout(() => {
resolve(ms);
console.log('waste', ms);
}, ms));
}
const arr = [3000, 4000, 5000, 3000];
arr.reduce(async (last, curr) => {
await last;
return wasteTime(curr);
}, undefined);Summary
Prefer Promise for any asynchronous code.
All Promise methods return a Promise.
State changes are one‑time; use reject(new Error(...)) for errors.
Always attach .then() and .catch() (or .finally() ).
Use Promise.all() to run multiple Promises in parallel.
Consider async/await for clearer control flow, but be aware of context‑dependent blocking.
Bilibili Tech
Provides introductions and tutorials on Bilibili-related technologies.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.