Source: index.js

/** @module promise-nodeify
 * @copyright Copyright 2016-2018 Kevin Locke <kevin@kevinlocke.name>
 * @license MIT
 */

'use strict';

/** Function which will run with a clear stack as soon as possible.
 * @private
 */
const later
  = typeof process !== 'undefined'
    && typeof process.nextTick === 'function' ? process.nextTick
    : typeof setImmediate === 'function' ? setImmediate
      : setTimeout;

/** Invokes callback and ensures any exceptions thrown are uncaught.
 * @private
 */
function doCallback(callback, reason, value) {
  // Note:  Could delay callback call until later, as When.js does, but this
  // loses the stack (particularly for bluebird long traces) and causes
  // unnecessary delay in the non-exception (common) case.
  try {
    // Match argument length to resolve/reject in case callback cares.
    // Note:  bluebird has argument length 1 if value === undefined due to
    // https://github.com/petkaantonov/bluebird/issues/170
    // If you are reading this and want similar behavior, I'll consider it.
    if (reason) {
      callback(reason);
    } else {
      callback(null, value);
    }
  } catch (err) {
    later(() => { throw err; });
  }
}

/** Calls a node-style callback when a Promise is resolved or rejected.
 *
 * This function provides the behavior of
 * {@link https://github.com/then/nodeify then <code>nodeify</code>},
 * {@link
 * https://github.com/cujojs/when/blob/master/docs/api.md#nodebindcallback
 * when.js <code>node.bindCallback</code>},
 * or {@link http://bluebirdjs.com/docs/api/ascallback.html bluebird
 * <code>Promise.prototype.nodeify</code> (now
 * <code>Promise.prototype.asCallback</code>)} (without options).
 *
 * @ template ValueType
 * @param {!Promise<ValueType>} promise Promise to monitor.
 * @param {?function(*, ValueType=)=} callback Node-style callback to be
 * called when <code>promise</code> is resolved or rejected.  If
 * <code>promise</code> is rejected with a falsey value the first argument
 * will be an instance of <code>Error</code> with a <code>.cause</code>
 * property with the rejected value.
 * @return {Promise<ValueType>|undefined} <code>undefined</code> if
 * <code>callback</code> is a function, otherwise a <code>Promise</code>
 * which behaves like <code>promise</code> (currently is <code>promise</code>,
 * but is not guaranteed to remain so).
 * @alias module:promise-nodeify
 */
function promiseNodeify(promise, callback) {
  if (typeof callback !== 'function') {
    return promise;
  }

  function onRejected(reason) {
    // callback is unlikely to recognize or expect a falsey error.
    // (we also rely on truthyness for arguments.length in doCallback)
    // Convert it to something truthy
    let truthyReason = reason;
    if (!truthyReason) {
      // Note:  unthenify converts falsey rejections to TypeError:
      // https://github.com/blakeembrey/unthenify/blob/v1.0.0/src/index.ts#L32
      // We use bluebird convention for Error, message, and .cause property
      truthyReason = new Error(String(reason));
      truthyReason.cause = reason;
    }

    doCallback(callback, truthyReason);
  }

  function onResolved(value) {
    doCallback(callback, null, value);
  }

  promise.then(onResolved, onRejected);
  return undefined;
}

/** A version of {@link promiseNodeify} which delegates to the
 * <code>.nodeify</code> method on <code>promise</code>, if present.
 *
 * This may be more performant than {@see promiseNodeify} and have additional
 * implementation-specific features, but the behavior may differ from
 * <code>promiseNodeify</code> and between Promise implementations.
 *
 * Note that this function only passes the callback argument to
 * <code>.nodeify</code>, since additional arguments are interpreted
 * differently by different libraries (e.g. bluebird treats the next argument
 * as an options object while then treats it as <code>this</code> for the
 * callback).
 *
 * @ template ValueType
 * @param {!Promise<ValueType>} promise Promise to monitor.
 * @param {?function(*, ValueType=)=} callback Node-style callback.
 * @return {Promise<ValueType>|undefined} Value returned by
 * <code>.nodeify</code>.  Known implementations return the
 * <code>promise</code> argument when callback is falsey and either
 * <code>promise</code> or <code>undefined</code> otherwise.
 */
promiseNodeify.delegated = function nodeifyDelegated(promise, callback) {
  if (typeof promise.nodeify === 'function') {
    return promise.nodeify(callback);
  }

  return promiseNodeify(promise, callback);
};

/** Polyfill for <code>Promise.prototype.nodeify</code> which behaves like
 * {@link promiseNodeify}.
 *
 * @ template ValueType
 * @this {!Promise<ValueType>}
 * @param {?function(*, ValueType=)=} callback Node-style callback.
 * @return {Promise<ValueType>|undefined} <code>undefined</code> if
 * <code>callback</code> is a function, otherwise a <code>Promise</code>
 * which behaves like <code>promise</code> (currently is <code>promise</code>,
 * but is not guaranteed to remain so).
 */
promiseNodeify.nodeifyThis = function nodeifyThis(callback) {
  return promiseNodeify(this, callback);
};

// Note: This file is used directly for Node and wrapped in UMD for browser
if (typeof exports === 'object') {
  module.exports = promiseNodeify;
}