NaturalCycles/js-lib

View on GitHub
src/error/tryCatch.ts

Summary

Maintainability
A
0 mins
Test Coverage
A
95%
import type { CommonLogger } from '../index'
import { _anyToError, _since } from '../index'
import type { AnyFunction } from '../types'

export interface TryCatchOptions {
  /**
   * The value returned from the function will be returned from the wrapped method (!).
   * onError function may be asynchronous.
   */
  onError?: (err: Error) => any

  /**
   * @default false
   */
  logSuccess?: boolean

  /**
   * @default true
   */
  logError?: boolean

  /**
   * Default to `console`
   */
  logger?: CommonLogger
}

/**
 * Decorates a function with "try/catch", so it'll never reject/throw.
 * Only applies to async functions (or, turns sync function into async).
 *
 * Allows to pass onError callback.
 *
 * @experimental
 */
export function _tryCatch<T extends AnyFunction>(fn: T, opt: TryCatchOptions = {}): T {
  const { onError, logError = true, logSuccess = false, logger = console } = opt

  const fname = fn.name || 'anonymous'

  return async function (this: any, ...args: any[]) {
    const started = Date.now()

    try {
      const r = await fn.apply(this, args)

      if (logSuccess) {
        logger.log(`tryCatch.${fname} succeeded in ${_since(started)}`)
      }

      return r
    } catch (err) {
      if (logError) {
        logger.warn(`tryCatch.${fname} error in ${_since(started)}:`, err)
      }

      if (onError) {
        try {
          return await onError(_anyToError(err)) // eslint-disable-line @typescript-eslint/return-await
        } catch {}
      }
      // returns undefined, but doesn't rethrow
    }
  } as any
}

// eslint-disable-next-line @typescript-eslint/naming-convention
export const _TryCatch =
  (opt: TryCatchOptions = {}): MethodDecorator =>
  (target, key, descriptor) => {
    const originalFn = descriptor.value
    descriptor.value = _tryCatch(originalFn as any, opt)
    return descriptor
  }