// Copyright 2013 Traceur Authors. // // Licensed under the Apache License, Version 2.0 (the “License”); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an “AS IS” BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License.

'use strict';

/**

* Wrap a single async function to make the callback optional and hook it to
* trigger reject/resolve of the Promise, which it returns (this ignores the
* async function's return value). This enables the use of await with the
* wrapped function.
*
* @param {Function} fn Function to wrap.
* @param {boolean} firstArg True if the async callback is the first argument.
* @return {Function}
*/

function wrapFunction(fn, firstArg) {

return function() {
  var resolve, reject;
  var promise = new Promise(function(res, rej) {
    resolve = res;
    reject = rej;
  });

  var args = [].slice.call(arguments);
  var originalCallback = args[firstArg ? 0 : args.length - 1];

  function callback(err, value) {
    if (originalCallback)
      originalCallback.apply(this, arguments);

    if (err)
      reject(err);
    else
      resolve(value);
  }

  if (typeof originalCallback !== 'function') {
    // Callback wasn't provided to the async function, add the custom one.
    originalCallback = null;
    if (firstArg)
      args.unshift(callback);
    else
      args.push(callback);
  } else {
    // Callback was provided to the async function, replace it.
    args[firstArg ? 0 : args.length - 1] = callback;
  }

  fn.apply(this, args);

  return promise;
};

}

/**

* Wrap async functions in a module to enable the use of await.
* If no function name array is provided, every function with a fnSync
* variant will be wrapped.
*
* @param {string|Object} module The exports of the module or a string that
*     will be passed to require to get the module.
* @param {Array.<string>} functions Function names to wrap.
* @return {object} The module.
*/

function wrapModule(module, functions) {

if (typeof module === 'string')
  module = require(module);

if (!functions) {
  for (var k in module) {
    // HACK: wrap all functions with a fnSync variant.
    if (typeof module[k] === 'function' &&
        typeof module[k + 'Sync'] === 'function')
      module[k] = wrapFunction(module[k]);
  }
} else {
  for (var i = 0, k; i < functions.length; i++) {
    var k = functions[i];
    module[k] = wrapFunction(module[k]);
  }
}

return module;

}

/**

* Wrap async functions in Node.js to enable the use of await.
*
* @return {void}
*/

function wrap() {

// TODO: find and wrap everything that needs to be wrapped.
wrapModule('fs');
process.nextTick = wrapFunction(process.nextTick, true);
// FIXME: this would ignore the return value, making it impossible to cancel
// the timeout without implementing a cancel method and using it everywhere.
//global.setTimeout = wrapFunction(setTimeout, true);

} exports.wrap = wrap;