Source: lib/disguise.js

/**
 * Generic `Promise.catch()` method
 *
 * It delegate its functionality on the `then()` of the object where it's
 * applied, both directly or on its class definition prototype
 *
 * @param {Function} [onRejected]
 *
 * @return {Promise}
 */
function promiseCatch(onRejected) {
  return this.then(null, onRejected)
}

//
// Public API
//

/**
 * Disguise an object giving it the appearance of another
 *
 * Add bind'ed functions and properties to a `target` object delegating the
 * actions and attributes updates to the `source` one while retaining its
 * original personality (i.e. duplicates and `instanceof` are preserved)
 *
 * @param {Object} target - the object to be disguised
 * @param {Object} source - the object where to fetch its methods and attributes
 * @param {Object} [unthenable] - the returned object should not be a thenable
 *
 * @return {Object} `target` disguised
 */
function disguise(target, source, unthenable) {
  if (source == null || target === source) return target

  for (var key in source) {
    if (target[key] !== undefined) continue
    if (unthenable && (key === 'then' || key === 'catch')) continue

    if (typeof source[key] === 'function')
      var descriptor = {
        value: source[key]
      }
    else
      var descriptor = {
        get: function () {
          return source[key]
        },
        set: function (value) {
          source[key] = value
        }
      }

    descriptor.enumerable = true

    Object.defineProperty(target, key, descriptor)
  }
  return target
}

/**
 * Disguise a thenable object
 *
 * If available, `target.then()` gets replaced by a method that exec the
 * `onFulfilled` and `onRejected` callbacks using `source` as `this` object, and
 * return the Promise returned by the original `target.then()` method already
 * disguised. It also add a `target.catch()` method pointing to the newly added
 * `target.then()`, being it previously available or not.
 *
 * @param {thenable} target - the object to be disguised
 * @param {Object} source - the object where to fetch its methods and attributes
 *
 * @return {thenable} `target` disguised
 */
function disguiseThenable(target, source) {
  if (target === source) return target

  if (target.then instanceof Function) {
    var target_then = target.then

    function then(onFulfilled, onRejected) {
      if (onFulfilled != null) onFulfilled = onFulfilled.bind(target)
      if (onRejected != null) onRejected = onRejected.bind(target)

      var promise = target_then.call(target, onFulfilled, onRejected)

      return disguiseThenable(promise, source)
    }

    Object.defineProperties(target, {
      then: {
        value: then
      },
      catch: {
        value: promiseCatch
      }
    })
  }

  return disguise(target, source)
}

/**
 * Return a copy of the input object without `.then()` and `.catch()`
 *
 * @param {thenable} input
 *
 * @return {Object} unthenabled input object
 */
function unthenable(input) {
  var output = Object.assign({}, input)
  delete output.then
  if (input !== undefined)
    output.constructor = input.constructor

  if (input && input.then instanceof Function) return disguise(output, input,
    true)

  // `input` is not thenable
  return input
}

disguiseThenable.disguise = disguise
disguiseThenable.disguiseThenable = disguiseThenable
disguiseThenable.unthenable = unthenable

module.exports = disguiseThenable