BRUCE_FEBRUCE_FE

EN/CH Mode

BRUCE_FE JS Interview Notes - Hand-written Promise Implementation

Learn how to implement JavaScript Promise from scratch, understand asynchronous processing principles, master Promise A+ specification, and enhance frontend interview competitiveness.

影片縮圖

Lazy to read articles? Then watch videos!

Promise Overview

Promise is the standard way to handle asynchronous operations in JavaScript, representing an operation that is not yet completed but will eventually complete. In interviews, hand-writing Promise implementation is a common topic to test understanding of asynchronous processing, event loops, and Promise A+ specification.

Three States of Promise

  • 1. pending: Initial state, neither success nor failure
  • 2. fulfilled: Operation successfully completed with a result value
  • 3. rejected: Operation failed with an error reason

Promise state transitions can only be:

  • 1. pending → fulfilled
  • 2. pending → rejected

Once a Promise state changes, it cannot be changed again. This characteristic is called 'immutability'.

Basic Promise Implementation

Below is a simplified Promise implementation with basic functionality:

class MyPromise {
  constructor(executor) {
    this.state = 'pending'       // Initial state
    this.value = undefined       // Value when successful
    this.reason = undefined      // Reason when failed
    this.onFulfilledCbs = []     // Array to store success callback functions
    this.onRejectedCbs = []      // Array to store failure callback functions

    // Success handling function
    const resolve = (value) => {
      if (this.state === 'pending') {
        queueMicrotask(() => {   // Ensure asynchronous execution
          this.state = 'fulfilled'
          this.value = value
          this.onFulfilledCbs.forEach(fn => fn())  // Execute all registered success callbacks
        })
      }
    }

    // Failure handling function
    const reject = (reason) => {
      if (this.state === 'pending') {
        queueMicrotask(() => {   // Ensure asynchronous execution
          this.state = 'rejected'
          this.reason = reason
          this.onRejectedCbs.forEach(fn => fn())  // Execute all registered failure callbacks
        })
      }
    }

    // Immediately execute the passed function, catch possible errors
    try {
      executor(resolve, reject)
    } catch (e) {
      reject(e)
    }
  }

  // Core method for chaining calls
  then(onFulfilled, onRejected) {
    // Parameter processing, ensure onFulfilled and onRejected are functions
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
    onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e }

    // Return new Promise to implement chaining
    return new MyPromise((resolve, reject) => {
      // Unified callback logic handling
      const handle = (callback, val, resolver, rejecter) => {
        queueMicrotask(() => {
          try {
            const result = callback(val)
            // Handle case where callback returns a Promise
            result instanceof MyPromise
              ? result.then(resolver, rejecter)
              : resolver(result)
          } catch (err) {
            rejecter(err)
          }
        })
      }

      // Decide how to handle based on current Promise state
      if (this.state === 'fulfilled') {
        handle(onFulfilled, this.value, resolve, reject)
      } else if (this.state === 'rejected') {
        handle(onRejected, this.reason, resolve, reject)
      } else {
        // Promise is still in pending state, store callbacks in corresponding arrays
        this.onFulfilledCbs.push(() => {
          handle(onFulfilled, this.value, resolve, reject)
        })
        this.onRejectedCbs.push(() => {
          handle(onRejected, this.reason, resolve, reject)
        })
      }
    })
  }
}