Promise

Promises, together with async/await, have become the go-to tools for handling concurrency in modern JavaScript development.

basics

Promise can be in one of three distinct states

  • pending: The operation is still ongoing,
  • fulfilled (resolved): The operation has completed successfully.
  • rejected: The operation failed.

consuming a Promise

promise.then(value => console.log(value), err => console.error(err))
const main = async () => {
  try {
    console.log(await promise)
  } catch (err) {
    console.error(err)
  }
}
const inc = x => x + 1

promise.then(inc).then(inc).then(console.log)

creating a Promise

an async function allways returns a Promise

const asyncValue = async <T>(val: T): Promise<T> => val

asyncValue(1).then(console.log)

creating a Promise using the constructor

const delay = (time: number): Promise<void> => {
  return new Promise((resolve, reject) => setTimeout(() => resolve(), time))
}

await delay(1000)
// 1 seconds later

a simplified Promise implementation

type Callback<T=any> = (value: T) => any

function isPromsie(value: any): boolean {
  return typeof value?.then === 'function'
}

export class SimplePromise<T> {

  state: 'pending' | 'fulfilled' | 'rejected' = 'pending'
  value: T
  reason: any
  onFulfilled: Array<Callback<T>> = []
  onRejected: Array<Callback> = []

  constructor(executor: (resolve: Callback<T>, reject: Callback) => void) {
    const resolve = (value: T) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled'
        this.value = value
        this.onFulfilled.forEach(cb => cb(value))
      }
    }

    const reject = (reason: any) => {
      if (this.state === 'pending') {
        this.state = 'rejected'
        this.reason = reason
        this.onRejected.forEach(cb => cb(reason))
      }
    }

    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error)
    }
  }

  then(onFulfilled?: Callback<T>, onRejected?: Callback) {
    return new SimplePromise((resolve, reject) => {
      const handleFulfilled = (value: T) => {
        if (onFulfilled) {
          try {
            const result = onFulfilled(value)
            isPromsie(result) ? result.then(resolve, reject) : resolve(result)
          } catch (error) {
            reject(error)
          }
        } else {
          resolve(value)
        }
      }

      const handleRejected = (reason: any) => {
        if (onRejected) {
          try {
            const result = onRejected(reason)
            isPromsie(result) ? result.then(resolve, reject) : resolve(result)
          } catch (error) {
            reject(error)
          }
        } else {
          reject(reason)
        }
      }

      if (this.state === 'fulfilled') {
        handleFulfilled(this.value)
      } else if (this.state === 'rejected') {
        handleRejected(this.reason)
      } else if (this.state === 'pending') {
        this.onFulfilled.push(handleFulfilled)
        this.onRejected.push(handleRejected)
      }
    })
  }

  catch(onRejected: Callback) {
    return this.then(undefined, onRejected)
  }
}