promise/promisifyAll.ts

/** @module Promise */

import { isObj } from '@object/isObj'
import { isFunc } from '@method/isFunc'
import { promisify } from './promisify'

/**
 * Creates an array of Object default properties not to convert into promises
 * @ignore
 */
const defObjProps = Array.from([
  'caller',
  'callee',
  'arguments',
  'apply',
  'bind',
  'call',
  'toString',
  '__proto__',
  '__defineGetter__',
  '__defineSetter__',
  'hasOwnProperty',
  '__lookupGetter__',
  '__lookupSetter__',
  'isPrototypeOf',
  'propertyIsEnumerable',
  'valueOf',
  'toLocaleString',
])
  .concat(Object.getOwnPropertyNames(Object.prototype))
  .reduce((map, functionName) => {
    map[functionName] = true
    return map
  }, {})

/**
 * Loops an object and looks for any methods that belong to the object, then add an Async version
 * @param {Object} object
 * @return {Object} - object with Async methods added
 * @private
 */
const addAsync = (object: any): any => {
  if (!object.__IS_PROMISIFIED__) {
    for (const prop of Object.getOwnPropertyNames(object)) {
      const isAsync = prop.indexOf('Async') !== -1 || object[`${prop}Async`]
      if (isAsync || defObjProps[prop]) continue

      if (isFunc(object[prop])) object[`${prop}Async`] = promisify(object[prop])
      else {
        const getValue = Object.getOwnPropertyDescriptor(object, prop).get
        if (isFunc(getValue)) object[`${prop}Async`] = promisify(getValue)
      }
    }
    object.__IS_PROMISIFIED__ = true
  }

  return object
}

/**
 * Converts Objects method properties into promiseAsync. allow using promisifyAll
 * @function
 * @param {Object} object
 * @return {Object} - promisified object
 */
export const promisifyAll = <T = Record<string, any>>(
  object: Record<string, any>
): T => {
  if (!isObj(object)) return object as T

  addAsync(object)
  const proto = Object.getPrototypeOf(object)

  proto && Object.getPrototypeOf(proto) !== null && addAsync(proto)

  return object as T
}