Hone logo
Hone
Problems

Implementing a Promise/A+ Compliant Promise in JavaScript

This challenge asks you to implement a robust and fully compliant Promise/A+ specification in JavaScript. Promises are a fundamental part of asynchronous programming, and understanding their underlying implementation provides valuable insight into how asynchronous operations are managed in JavaScript. Successfully completing this challenge will demonstrate a deep understanding of asynchronous JavaScript and the Promise/A+ standard.

Problem Description

Your task is to create a Promise class that adheres to the Promise/A+ specification. This includes implementing all the required methods and behaviors outlined in the specification. The Promise class should handle asynchronous operations, resolve or reject based on the outcome of those operations, and properly chain and manage callbacks. You must implement the core functionality of a Promise, including resolving, rejecting, chaining, and handling errors.

Key Requirements:

  • new Promise(executor): The constructor should accept an executor function. The executor function should receive two arguments: resolve and reject, which are functions used to signal the fulfillment or rejection of the promise, respectively.
  • then(onFulfilled, onRejected): This method should be called when the promise is resolved or rejected. It should return a new promise. If onFulfilled is provided, it should be called with the resolved value. If onRejected is provided, it should be called with the rejected reason. If neither is provided, the value/reason should be passed to the next then call. It must handle both synchronous and asynchronous onFulfilled and onRejected callbacks correctly.
  • catch(onRejected): This method is equivalent to then(null, onRejected). It should return a new promise.
  • finally(onFinally): This method should be called regardless of whether the promise is resolved or rejected. It should return a new promise. The onFinally callback should not receive the resolved value or rejection reason. It should not prevent the promise from resolving or rejecting.
  • delete: (Optional, but highly recommended for full compliance) The delete method should remove a rejected handler from the promise's internal queue. This is important for preventing memory leaks.
  • Error Handling: Uncaught rejections should be handled appropriately (e.g., logged to the console).
  • Chaining: Promises should be chainable. Multiple then calls should be executed sequentially.
  • Asynchronous Execution: Callbacks should be executed asynchronously, using setTimeout or setImmediate to avoid blocking the main thread.
  • Multiple thens: Multiple then calls should be queued and executed in order.
  • Rejection Propagation: Rejections should propagate down the chain until a handler is found.
  • Promise Status: The promise should maintain a consistent state (pending, fulfilled, rejected).

Expected Behavior:

  • A new promise should be created when then, catch, or finally is called.
  • Callbacks should be executed asynchronously.
  • Rejections should propagate down the chain.
  • The promise should transition between states correctly.
  • The executor function should be called immediately upon promise creation.

Edge Cases to Consider:

  • executor throwing an error.
  • onFulfilled or onRejected throwing an error.
  • Chaining multiple promises.
  • Resolving/rejecting a promise with another promise.
  • Resolving/rejecting a promise with null or undefined.
  • Multiple then calls.
  • Promises resolving/rejecting after already resolved/rejected.

Examples

Example 1:

Input:
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('Success!');
  }, 100);
});

promise.then(value => {
  console.log(value); // Output: Success!
});

Output: Success! Explanation: The promise resolves after 100ms, and the then callback is executed with the resolved value.

Example 2:

Input:
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('Error!');
  }, 100);
});

promise.catch(error => {
  console.log(error); // Output: Error!
});

Output: Error! Explanation: The promise rejects after 100ms, and the catch callback is executed with the rejection reason.

Example 3:

Input:
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('First');
  }, 50);
});

promise.then(value => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('Second');
    }, 50);
  });
}).then(value => {
  console.log(value); // Output: Second
});

Output: Second Explanation: Demonstrates promise chaining with an asynchronous callback in the first then.

Constraints

  • Time Complexity: The then, catch, and finally methods should have a time complexity of O(1).
  • Space Complexity: The space complexity should be minimized to avoid unnecessary memory usage.
  • Browser Compatibility: The implementation should be compatible with modern browsers (ES6+).
  • Asynchronous Execution: Callbacks must be executed asynchronously. Using setTimeout(..., 0) or setImmediate() is required. Directly calling the callback will result in failure.
  • No External Libraries: You are not allowed to use any external libraries.

Notes

  • The Promise/A+ specification is available online: https://promisesaplus.github.io/promises-spec/ Refer to it for detailed requirements.
  • Consider using a queue to manage callbacks.
  • Pay close attention to the asynchronous execution of callbacks.
  • Thoroughly test your implementation with various scenarios, including edge cases.
  • The delete method is optional but demonstrates a deeper understanding of the specification and helps prevent memory leaks. It's highly recommended.
  • Focus on correctness and adherence to the specification. Performance is secondary, but strive for reasonable efficiency.
Loading editor...
javascript