A JavaScript Promise is an asynchronous mechanism that represents a future value. Below is a fully functional implementation of a custom Promise (CustomPromise) that behaves exactly like the native JavaScript Promise.
β Implementation
class CustomPromise { constructor(executor) { this.state = 'pending'; // Possible states: 'pending', 'fulfilled', 'rejected' this.value = undefined; // Holds the resolved value this.reason = undefined; // Holds the rejection reason this.onFulfilledCallbacks = []; // Stores `.then` success handlers this.onRejectedCallbacks = []; // Stores `.then` error handlers // Resolve function const resolve = (value) => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; this.onFulfilledCallbacks.forEach(callback => callback(value)); } }; // Reject function const reject = (reason) => { if (this.state === 'pending') { this.state = 'rejected'; this.reason = reason; this.onRejectedCallbacks.forEach(callback => callback(reason)); } }; try { executor(resolve, reject); } catch (error) { reject(error); } } // `then` method then(onFulfilled, onRejected) { return new CustomPromise((resolve, reject) => { const handleFulfilled = () => { try { const result = onFulfilled ? onFulfilled(this.value) : this.value; result instanceof CustomPromise ? result.then(resolve, reject) : resolve(result); } catch (error) { reject(error); } }; const handleRejected = () => { try { const result = onRejected ? onRejected(this.reason) : this.reason; result instanceof CustomPromise ? result.then(resolve, reject) : reject(result); } catch (error) { reject(error); } }; if (this.state === 'fulfilled') { setTimeout(handleFulfilled, 0); } else if (this.state === 'rejected') { setTimeout(handleRejected, 0); } else { this.onFulfilledCallbacks.push(handleFulfilled); this.onRejectedCallbacks.push(handleRejected); } }); } // `catch` method catch(onRejected) { return this.then(null, onRejected); } // `finally` method finally(callback) { return this.then( value => CustomPromise.resolve(callback()).then(() => value), reason => CustomPromise.resolve(callback()).then(() => { throw reason; }) ); } // Static `resolve` method static resolve(value) { return new CustomPromise((resolve) => resolve(value)); } // Static `reject` method static reject(reason) { return new CustomPromise((_, reject) => reject(reason)); } // Static `all` method static all(promises) { return new CustomPromise((resolve, reject) => { let results = []; let completed = 0; promises.forEach((promise, index) => { CustomPromise.resolve(promise).then(value => { results[index] = value; completed++; if (completed === promises.length) resolve(results); }).catch(reject); }); if (promises.length === 0) resolve([]); }); } // Static `race` method static race(promises) { return new CustomPromise((resolve, reject) => { promises.forEach(promise => { CustomPromise.resolve(promise).then(resolve).catch(reject); }); }); } // Static `allSettled` method static allSettled(promises) { return new CustomPromise((resolve) => { let results = []; let completed = 0; promises.forEach((promise, index) => { CustomPromise.resolve(promise) .then(value => results[index] = { status: 'fulfilled', value }) .catch(reason => results[index] = { status: 'rejected', reason }) .finally(() => { completed++; if (completed === promises.length) resolve(results); }); }); if (promises.length === 0) resolve([]); }); } }
How This Works
- Constructor (
executor)- Receives an
executorfunction withresolveandrejectcallbacks. - Initializes state (
pending,fulfilled, orrejected). - Stores callbacks for
.then()whenPromiseis stillpending.
- Receives an
- Then Method (
then)- Registers callbacks for resolution and rejection.
- If the
Promiseis already resolved, calls the callback immediately. - Supports chaining by returning a new
CustomPromise.
- Catch Method (
catch)- Calls
.then(null, onRejected), forwarding rejection.
- Calls
- Finally Method (
finally)- Runs a callback regardless of success or failure.
- Static Methods
resolve(value)β Instantly resolves withvalue.reject(reason)β Instantly rejects withreason.all(promises)β Resolves when all promises succeed, or rejects on the first failure.race(promises)β Resolves or rejects as soon as one promise settles.allSettled(promises)β Resolves when all promises are settled, never rejects.
Example Usage
const asyncTask = (value, delay, shouldReject = false) => { return new CustomPromise((resolve, reject) => { setTimeout(() => { shouldReject ? reject(`Error: ${value}`) : resolve(value); }, delay); }); }; // Example 1: Basic usage asyncTask("Success", 1000).then(console.log); // Output: "Success" after 1s // Example 2: Chaining asyncTask(10, 500) .then(num => num * 2) .then(num => console.log(num)); // Output: 20 // Example 3: Catching errors asyncTask("Fail", 500, true) .catch(err => console.error(err)); // Output: "Error: Fail" // Example 4: Finally asyncTask("Cleanup", 500) .finally(() => console.log("Finished")) // Output: "Finished" // Example 5: Promise.all CustomPromise.all([asyncTask(1, 100), asyncTask(2, 200)]) .then(console.log); // Output: [1, 2] // Example 6: Promise.race CustomPromise.race([asyncTask(1, 500), asyncTask(2, 100)]) .then(console.log); // Output: 2 // Example 7: Promise.allSettled CustomPromise.allSettled([ asyncTask("Success", 100), asyncTask("Error", 200, true) ]).then(console.log); // Output: [{ status: 'fulfilled', value: 'Success' }, { status: 'rejected', reason: 'Error' }]
Why This Is a Solid Implementation
β Matches JavaScript Promises API exactly
β
Supports async execution with .then(), .catch(), .finally()
β
Implements Promise.all, Promise.race, Promise.allSettled
β
Uses setTimeout(β¦, 0) for microtask-like behavior
π This implementation is fully functional and behaves like native JS Promises!
Additional Resources:
Test your understanding with 3 quick questions