errors/AwaitingPromiseError.js

import { AsyncFunctionExecutionError } from './AsyncFunctionExecutionError'

/**
 * An error that occurs while waiting for a Promise or asynchronous function
 * to resolve. Typically found in the catch() handler of a promise or the
 * try/catch handler of an `await asyncFn()` statement. In addition to an
 * error message that occurred during the asynchronous event, this error
 * can track other potentially pertinent information such as the promise
 * in question and/or an AsyncFunctionExecutionError wrapping the originally
 * thrown generic error.
 *
 * @class AwaitingPromiseError
 */
export class AwaitingPromiseError extends Error {
  /**
   * The promise that threw an error if available.
   *
   * @type {Promise<mixed>}
   * @instance
   * @memberOf AwaitingPromiseError
   */
  promise: Promise<mixed>

  /**
   * The error that spawned the creation of this Error instance. Specifically,
   * it is better if this error is an instance of AsyncFunctionExecutionError
   *
   * @type {Error}
   * @instance
   * @memberOf AwaitingPromiseError
   */
  error: Error

  /**
   * Creates a new instance of AwaitingPromiseError. It takes the same default
   * parameters of the core Error class.
   *
   * @constructor
   *
   * @param {Error|string} error the error or message to describe what happened
   * @param {string} fileName the name of the file, see Error for more detail
   * @param {number} lineNumber line number of the error, see Error for more
   */
  constructor(
    error: ?Error|string,
    fileName: ?string,
    lineNumber: number = NaN
  ) {
    let message = error instanceof Error ? error.message : error

    super(message || error, fileName, lineNumber)

    if (error instanceof Error) {
      let { columnNumber, fileName, lineNumber, message, name, stack } = error

      Object.assign(this, {
        columnNumber, fileName, lineNumber, message, name, stack
      })

      this.error = error
    }
  }

  /**
   * A promise that potentially spawned the error is something that this type
   * of Error tracks. You can set this inline with a return statement by using
   * this helper function.
   *
   * i.e.
   * ```
   * let promise; // define elsewhere
   * promise
   *   .then(result => ({}))
   *   .catch(error => {
   *     throw new AwaitingPromiseError(error).setPromise(promise)
   *   })
   * ```
   *
   * @method ⌾⠀setPromise
   * @memberof AwaitingPromiseError
   * @instance
   *
   * @param {Promise<mixed>} promise the promise to store as a chained call
   * @return {AwaitingPromiseError} `this` for use in a chained function call
   */
  setPromise(promise: Promise<mixed>): AwaitingPromiseError {
    this.promise = promise
    return this
  }

  /**
   * A static helper method that creates an error but also takes and records
   * the additional data with regards to the associated async function call
   * that caused the error to occur. Optionally a promise can be assigned by
   * giving one as the last parameter.
   *
   * @method ⌾⠀asyncFn
   * @memberof AwaitingPromiseError
   * @instance
   *
   * @param {Error|string} error any error present at the time of failure
   * @param {Function} fn the function that failed
   * @param {Array<mixed>} args any arguments passed to the failed fn
   * @param {mixed} result any result returned by the failed function
   * @param {Promise<mixed>} promise an associated promise, if available
   *
   * @return {AwaitingPromiseError} a newly minted AwaitingPromiseError with
   * all the specified data
   */
  static asyncFn(
    error: (Error | string),
    fn: ?Function,
    args: ?Array<mixed>,
    result: ?mixed,
    promise: ?Promise<mixed>
  ): AwaitingPromiseError {
    let preciseError = new AsyncFunctionExecutionError(error, fn, args, result)
    let finalError = new AwaitingPromiseError(preciseError)

    if (promise) {
      finalError.setPromise(promise)
    }

    return finalError
  }
}

export default AwaitingPromiseError