Crafting Your Own Promise: A JavaScript Challenge
Promises are a cornerstone of asynchronous JavaScript, providing a cleaner way to handle operations that don't complete immediately. This challenge asks you to build your own simplified Promise implementation from scratch, deepening your understanding of how Promises work under the hood. Successfully completing this challenge will solidify your grasp of asynchronous programming concepts.
Problem Description
Your task is to create a MyPromise class that mimics the core functionality of the built-in Promise object. This includes handling asynchronous operations, resolving or rejecting promises, and chaining operations. Your MyPromise should support the following:
- Constructor: Accepts a function called the "executor." The executor function should take two arguments:
resolveandreject, which are functions used to signal the completion (resolution) or failure (rejection) of the promise. .then(onFulfilled, onRejected): Attaches a fulfillment or rejection handler to the promise.onFulfilledis called when the promise is resolved. It receives the resolved value as an argument.onRejectedis called when the promise is rejected. It receives the rejection reason as an argument..then()should always return a newMyPromiseobject, regardless of whether the original promise resolved or rejected. This allows for chaining.
.catch(onRejected): Attaches a rejection handler to the promise. This is equivalent to.then(null, onRejected)..finally(onFinally): Attaches a handler to be executed when the promise settles (either resolves or rejects). TheonFinallyhandler should be executed regardless of whether the promise resolves or rejects. It should not receive any arguments.- State Management: Internally, your
MyPromiseshould manage its state:pending: Initial state, neither resolved nor rejected.fulfilled: Promise has resolved.rejected: Promise has rejected.
- Asynchronous Execution: The executor function passed to the constructor must be executed asynchronously. You can achieve this using
setTimeoutwith a delay of 0ms. This ensures the executor doesn't block the main thread. - Chaining: When a
.then()handler returns a value, that value should be used as the resolved value of the promise returned by.then(). If a.then()handler throws an error, that error should be used as the rejected reason of the promise returned by.then().
Examples
Example 1:
const myPromise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('Success!');
}, 100);
});
myPromise.then(value => {
console.log(value); // Output: Success!
}).then(value => {
console.log("Second then:", value); // Output: Second then: undefined
});
Explanation: The promise resolves after 100ms. The first .then() handler logs "Success!". The second .then() handler receives undefined because the first handler didn't explicitly return a value.
Example 2:
const myPromise = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject('Error!');
}, 100);
});
myPromise.catch(error => {
console.log(error); // Output: Error!
});
Explanation: The promise rejects after 100ms. The .catch() handler logs "Error!".
Example 3: (Chaining with error handling)
const myPromise = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject('Initial Error!');
}, 50);
});
myPromise.then(value => {
return value + " - Success";
})
.catch(error => {
console.log("First catch:", error); // Output: First catch: Initial Error!
})
.then(value => {
console.log("Second then:", value); // Output: Second then: undefined
})
.finally(() => {
console.log("Finally executed"); // Output: Finally executed
});
Explanation: The promise rejects immediately. The first .catch() handler logs "Initial Error!". The second .then() is not executed because the promise rejected. The .finally() handler is executed regardless of the outcome.
Constraints
- Your
MyPromiseclass should not rely on the built-inPromiseobject. - The executor function must be executed asynchronously using
setTimeout(..., 0). - The
.then()method must always return a newMyPromiseinstance. - The
finallymethod should not receive any arguments. - The code should be well-structured and readable.
Notes
- Focus on implementing the core functionality of Promises. You don't need to handle all edge cases or advanced features (e.g.,
Promise.all,Promise.race). - Think carefully about how to manage the promise's state and how to handle asynchronous operations.
- Consider how to chain promises together and how to handle errors.
- Debugging asynchronous code can be tricky. Use
console.logstatements strategically to track the flow of execution. - Pay close attention to the return values of
.then()handlers and how they affect the resolution of subsequent promises.