delayCancellable.mjs


import Deferred from './Deferred.mjs'
import CancelledError from './CancelledError.mjs'
import setImmediate from 'core-js-pure/features/set-immediate.js'
import clearImmediate from 'core-js-pure/features/clear-immediate.js'

/**
 * A function returning a promise that will be resolved in a later tick of the event loop.
 *
 * This function returns both a promise and cancel function in order to cancel the wait time if
 * necessary. If cancelled, the promise will be rejected with a CancelledError.
 *
 * This function uses core-js' shim for `setImmediate()` internally.
 *
 * @returns {Array} A tuple of two objects:
 *   * The promise
 *   * The cancel function. It will return a boolean that will be true if the promise was effectively cancelled,
 *     false otherwise.
 * @example
 * import { delayCancellable, CancelledError } from 'modern-async'
 *
 * const [promise, cancel] = delayCancellable()
 * cancel()
 * try {
 *   await promise
 * } catch (e) {
 *   console.log(e instanceof CancelledError) // prints true
 * }
 */
function delayCancellable () {
  const deferred = new Deferred()
  const id = setImmediate(deferred.resolve)
  let terminated = false
  return [deferred.promise.finally(() => {
    terminated = true
  }), () => {
    if (terminated) {
      return false
    } else {
      terminated = true
      deferred.reject(new CancelledError())
      clearImmediate(id)
      return true
    }
  }]
}

export default delayCancellable