Table of contents
    My current understanding
    Breaking it down
    Chaining .then callbacks:
    Flattening the .then
    Promises

    Promises

    .08 May, 2025

    Let's build Promise from scratch.

    My current understanding

    The use of promises is basically to add a function to javascript's task queue once a value has been obtained by a long-running process (that process is usually carried out in a different thread or other part of the os, like when we request some value over the network or from hard disk).

    let p = new Promise((resolve,reject) => {
      try {
        const value = //obtain some value from a long running task.
        resolve(value)
      } catch (e) {
        reject(e)
      }
    })
    

    Now p is container for the value we actually want. Later in our code, when we want to do something with that value we can do that like so:

    p.then((val) => {
      // do something with this value
    })
    

    what we've done above is basically we've added the function (given to .then as an argument) to javascript's task queue. The event loop will pick it up when it gets time and add it to the call stack to be executed.

    Breaking it down

    So a promise is container for a value. It's constructor takes in a function. That function gets two arguments - a resolve function and a reject function. A promise also has a state indicating whether it is pending, fulfilled (resolve has been called), or rejected (reject has been called).

    Each time we call .then on the promise we are adding a function to a list. When the promise is fulfilled we need to execute all the functions in that list.

    We'll implement the constructor first:

    class MyPromise {
      constructor(func) {
        this.value = undefined
        this.state = "pending"
        this.fulfillmentTasks = []
        this.rejectionTasks = []
        this.resolve = this.resolve.bind(this)
        this.reject= this.reject.bind(this)
        if (func) {
          
          func(this.resolve,this.reject)
        }
      }
      resolve(value) {
          if (this.state !== "pending") return this 
          this.state = "fulfilled"
          this.value = value
          this.rejectionTasks = []
          this.fulfillmentTasks.map(t => setTimeout(t, 0)) 
          this.fullfillmentTasks = []
          return this
        }
      reject(value) {
          if (this.state !== "pending") return this 
          this.state = "rejected"
          this.value = value
          this.fullfillmentTasks = []
          this.rejectionTasks.map(t => setTimeout(t, 0)) 
          this.rejectionTasks = []
          return this
        }
    }
    

    Implementing .then

    MyPromise.prototype.then = function(onFulfilled, onRejected) {
        switch (this.state) {
          case "pending": {
            this.fulfillmentTasks.push(() => onFulfilled(this.value))
            this.rejectionTasks.push(() => onRejected(this.value))  
            break
          }
          case "fulfilled": 
            setTimeout(() => onFulfilled(this.value), 0)
            break
          case "rejected":
            setTimeout(() => onRejected(this.value), 0)
            break
        }
    }
    
    
    const p = new MyPromise((res,rej) => {
      res(2)
    })
    
    p.then((val) => console.log(val))
    p.then((val) => console.log(val*2))
    p.then((val) => console.log(val*3))
    p.then((val) => console.log(val*4))
    
    

    Chaining .then callbacks:

    const p = new Promise((res,rej) => {
      res(2)
    })
    
    p.then((val) => val*2).then((val) => val*2).then((val) => val*2).then((val) => console.log(val))
    
    // should print 16
    

    For this we need to return a promise from .then function

    MyPromise.prototype.then = function(onFulfilled, onRejected) {
        const newPromise = new MyPromise()
    
        const fullfillmentTask = () => {
          const newValue = onFulfilled(this.value)
          newPromise.resolve(newValue)
        } 
        switch (this.state) {
          case "pending": {
            this.fulfillmentTasks.push(fullfillmentTask)
            this.rejectionTasks.push(() => onRejected(this.value))  
            break
          }
          case "fulfilled": 
            setTimeout(fullfillmentTask, 0)
            break
          case "rejected":
            setTimeout(() => onRejected(this.value), 0)
            break
        }
        return newPromise
    }
    
    
    const newp = new MyPromise((res,rej) => {
      res(2)
    })
    
    newp.then((val) => val*2).then((val) => val*2).then((val) => val*2).then((val) => console.log("val ->", val))
    
    module.exports.MyPromise = MyPromise
    

    Flattening the .then

    const p = new MyPromise((res) => {
      const p1 = new MyPromise((r) => r(2))
      res(p1)
    })
    
    p.then((val) => val.then(v =>  console.log(v)))
    //prints 2
    
    // But what we want is :
    p.then(val => console.log(val))
    //prints 2
    
    //so in effect p "becomes" p1
    

    To handle the above scenario:

    • p needs to "become" p1
    • p's value is p1's value
    • p's state is p1's state
    • we can't call p.resolve() once p1 resolves
    const imp = require("./main.js")
    const MyPromise = imp.MyPromise
    function isThenable(val) {
      return typeof val === "object" && val !== null && typeof val.then === "function"
    }
    
    MyPromise.prototype.fulfill = function(val) {
      if (isThenable(val)) return
      this.value = val
      this.state = "fulfilled"
      this.rejectionTasks = []
      this.fulfillmentTasks.map(t => setTimeout(t, 0)) 
      this.fullfillmentTasks = []
    }
    
    MyPromise.prototype.resolve = function(value) {
      if (this.settled) return this
      this.settled = true
    
      if (isThenable(value)) {
        value.then((v) => this.fulfill(v))
      } else {
        this.fulfill(value)
      }
      return this
    }
    
    
    //testing the flattening pattern.
    
    const p0 = new MyPromise((res) => {
      const p1 = new MyPromise((r) => r(2))
      res(p1)
    })
    
    p0.then(val => console.log("flattened val -> ", val))
    
    1let p = new Promise((resolve,reject) => {
    2  try {
    3    const value = //obtain some value from a long running task.
    4    resolve(value)
    5  } catch (e) {
    6    reject(e)
    7  }
    8})
    1p.then((val) => {
    2  // do something with this value
    3})
    1const p = new Promise((res,rej) => {
    2  res(2)
    3})
    4
    5p.then((val) => val*2).then((val) => val*2).then((val) => val*2).then((val) => console.log(val))
    6
    7// should print 16
    1const p = new MyPromise((res) => {
    2  const p1 = new MyPromise((r) => r(2))
    3  res(p1)
    4})
    5
    6p.then((val) => val.then(v =>  console.log(v)))
    7//prints 2
    8
    9// But what we want is :
    10p.then(val => console.log(val))
    11//prints 2
    12
    13//so in effect p "becomes" p1
    Abhimanyu