createManualPromise

Create a promise that you can resolve outside the Promise's executor function (i.e. the callback argument passed to new Promise()). This is mostly just useful for making your code 1 level less nested, or if you're allergic to async and await syntax.

typescriptjavascript
export type ManualPromise<T> = {
  promise: Promise<T>
  resolve: (value: T | PromiseLike<T>) => void
  reject: (reason?: unknown) => void
}
 
export const createManualPromise = <T>(): ManualPromise<T> => {
  let resolve: ManualPromise<T>['resolve'] | undefined
  let reject: ManualPromise<T>['reject'] | undefined
 
  const promise = new Promise<T>((res, rej) => {
    resolve = res
    reject = rej
  })
 
  return { promise, resolve: resolve!, reject: reject! }
}
export const createManualPromise = () => {
  let resolve
  let reject
 
  const promise = new Promise((res, rej) => {
    resolve = res
    reject = rej
  })
 
  return { promise, resolve: resolve, reject: reject }
}

Examples

Wait for a message from a worker thread

main.ts
typescriptjavascript
export const getAnswerToTheUltimateQuestionOfLifeTheUniverseAndEverythingFromWorker =
  (): Promise<any> => {
    const { promise, resolve, reject } = createManualPromise<any>()
    const worker = new Worker(
      new URL('./deep-thought-worker.ts', import.meta.url)
    )
 
    worker.onmessage = event => {
      resolve(event.data)
      worker.terminate()
    }
 
    worker.onerror = error => {
      reject(error)
      worker.terminate()
    }
 
    worker.postMessage(null)
 
    return promise
  }
export const getAnswerToTheUltimateQuestionOfLifeTheUniverseAndEverythingFromWorker =
  () => {
    const { promise, resolve, reject } = createManualPromise()
    const worker = new Worker(
      new URL('./deep-thought-worker.ts', import.meta.url)
    )
 
    worker.onmessage = event => {
      resolve(event.data)
      worker.terminate()
    }
 
    worker.onerror = error => {
      reject(error)
      worker.terminate()
    }
 
    worker.postMessage(null)
 
    return promise
  }
deeper-thought-worker.ts
typescriptjavascript
/**
 * Function that calculates the Answer to the Ultimate Question of Life, the
 * Universe, and Everything waaaaaaay faster than Deep Thought's 7.5 million
 * years.
 */
const calculateAnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything =
  (): number => {
    const start = Date.now()
    const end = start + 5_000
 
    while (Date.now() < end);
 
    return 0x6 * 0x9 === 0x2a ? 0x2a : 0x2a
  }
 
self.onmessage = () => {
  const answer =
    calculateAnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything()
  self.postMessage(answer)
}
/**
 * Function that calculates the Answer to the Ultimate Question of Life, the
 * Universe, and Everything waaaaaaay faster than Deep Thought's 7.5 million
 * years.
 */
const calculateAnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything =
  () => {
    const start = Date.now()
    const end = start + 5_000
 
    while (Date.now() < end);
 
    return 0x6 * 0x9 === 0x2a ? 0x2a : 0x2a
  }
 
self.onmessage = () => {
  const answer =
    calculateAnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything()
  self.postMessage(answer)
}

Waiting for a DOM event to happen

typescriptjavascript
export const waitUntilClicked = (selector: string): Promise<void> => {
  const { promise, resolve, reject } = createManualPromise<void>()
 
  const element = document.querySelector(selector)
  if (!element) {
    reject(new Error(`Element not found for selector: ${selector}`))
    return promise
  }
 
  element.addEventListener('click', () => {
    resolve()
  })
 
  return promise
}
export const waitUntilClicked = selector => {
  const { promise, resolve, reject } = createManualPromise()
 
  const element = document.querySelector(selector)
  if (!element) {
    reject(new Error(`Element not found for selector: ${selector}`))
    return promise
  }
 
  element.addEventListener('click', () => {
    resolve()
  })
 
  return promise
}

Simple fetch wrapper, but bad

typescriptjavascript
// Post from https://jsonplaceholder.typicode.com/
type Post = { userId: number; id: number; title: string; body: string }
export const fetchPosts = (): Promise<Post[]> => {
  const { promise, resolve, reject } = createManualPromise<Post[]>()
 
  fetch('https://jsonplaceholder.typicode.com/')
    .then(response => {
      if (res.ok) {
        return response.json()
      }
      return response
        .text()
        .then(text => Promise.reject(new Error(`Fetch posts error: ${text}`)))
    })
    .then(json => resolve(json as Post[]))
    .catch(error => reject(error))
 
  return promise
}
// Post from https://jsonplaceholder.typicode.com/
 
export const fetchPosts = () => {
  const { promise, resolve, reject } = createManualPromise()
 
  fetch('https://jsonplaceholder.typicode.com/')
    .then(response => {
      if (res.ok) {
        return response.json()
      }
      return response
        .text()
        .then(text => Promise.reject(new Error(`Fetch posts error: ${text}`)))
    })
    .then(json => resolve(json))
    .catch(error => reject(error))
 
  return promise
}
Created 20/06/23Updated 23/07/23
Found a mistake, or want to suggest an improvement? Source on GitHub here
and see edit history here