Source: util/sync-promise.js

/**
 * Copyright (C) 2015-2016 Regents of the University of California.
 * @author: Jeff Thompson <jefft0@remap.ucla.edu>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * A copy of the GNU Lesser General Public License is in the file COPYING.
 */

/** @ignore */
var NdnCommon = require('./ndn-common.js').NdnCommon;

/**
 * A SyncPromise is a promise which is immediately fulfilled or rejected, used
 * to return a promise in synchronous code.
 * This private constructor creates a SyncPromise fulfilled or rejected with the
 * given value. You should normally not call this constructor but call
 * SyncPromise.resolve or SyncPromise.reject. Note that we don't need a
 * constructor like SyncPromise(function(resolve, reject)) because this would be
 * for scheduling the function to be called later, which we don't do.
 * @param {any} value If isRejected is false, this is the value of the fulfilled
 * promise, else if isRejected is true, this is the error.
 * @param {boolean} isRejected True to create a promise in the rejected state,
 * where value is the error.
 * @constructor
 */
var SyncPromise = function SyncPromise(value, isRejected)
{
  this.value = value;
  this.isRejected = isRejected;
};

exports.SyncPromise = SyncPromise;

/**
 * If this promise is fulfilled, immediately call onFulfilled with the fulfilled
 * value as described below. Otherwise, if this promise is rejected, immediately
 * call onRejected with the error as described below.
 * @param {function} (optional) onFulfilled If this promise is fulfilled, this
 * calls onFulfilled(value) with the value of this promise and returns the
 * result. The function should return a promise. To use all synchronous code,
 * onFulfilled should return SyncPromise.resolve(newValue).
 * @param {function} (optional) onRejected If this promise is rejected, this
 * calls onRejected(err) with the error value of this promise and returns the
 * result. The function should return a promise. To use all synchronous code,
 * onFulfilled should return SyncPromise.resolve(newValue) (or throw an
 * exception).
 * @return {Promise|SyncPromise} If this promise is fulfilled, return the result
 * of calling onFulfilled(value). Note that this does not create a promise which
 * is scheduled to execute later. Rather it immediately calls onFulfilled which
 * should return a promise. But if onFulfilled is undefined, simply return this
 * promise to pass it forward. If this promise is rejected, return the result of
 * calling onRejected(err) with the error value. But if onRejected is undefined,
 * simply return this promise to pass it forward. However, if onFulfilled or
 * onRejected throws an exception, then return a new SyncPromise in the rejected
 * state with the exception.
 */
SyncPromise.prototype.then = function(onFulfilled, onRejected)
{
  if (this.isRejected) {
    if (onRejected) {
      try {
        return onRejected(this.value);
      }
      catch(err) {
        return new SyncPromise(err, true);
      }
    }
    else
      // Pass the error forward.
      return this;
  }
  else {
    if (onFulfilled) {
      try {
        return onFulfilled(this.value);
      }
      catch(err) {
        return new SyncPromise(err, true);
      }
    }
    else
      // Pass the fulfilled value forward.
      return this;
  }
};

/**
 * Call this.then(undefined, onRejected) and return the result. If this promise
 * is rejected then onRejected will process it. If this promise is fulfilled,
 * this simply passes it forward.
 */
SyncPromise.prototype.catch = function(onRejected)
{
  return this.then(undefined, onRejected);
};

/**
 * Return a new SyncPromise which is already fulfilled to the given value.
 * @param {any} value The value of the promise.
 */
SyncPromise.resolve = function(value)
{
  return new SyncPromise(value, false);
};

/**
 * Return a new SyncPromise which is already rejected with the given error.
 * @param {any} err The error for the rejected promise.
 */
SyncPromise.reject = function(err)
{
  return new SyncPromise(err, true);
};

/**
 * This static method checks if the promise is a SyncPromise and immediately
 * returns its value or throws the error if promise is rejected. If promise is
 * not a SyncPromise, this throws an exception since it is not possible to
 * immediately get the value. This can be used with "promise-based" code which
 * you expect to always return a SyncPromise to operate in synchronous mode.
 * @param {SyncPromise} promise The SyncPromise with the value to get.
 * @return {any} The value of the promise.
 * @throws Error If promise is not a SyncPromise.
 * @throws any If promise is a SyncPromise in the rejected state, this throws
 * the error.
 */
SyncPromise.getValue = function(promise)
{
  if (promise instanceof SyncPromise) {
    if (promise.isRejected)
      throw promise.value;
    else
      return promise.value;
  }
  else
    throw new Error("Cannot return immediately because promise is not a SyncPromise");
};

/**
 * This can be called with complete(onComplete, promise) or
 * complete(onComplete, onError, promise) to handle both synchronous and
 * asynchronous code based on whether the caller supplies the onComlete callback.
 * If onComplete is defined, call promise.then with a function which calls
 * onComplete(value) when fulfilled (possibly in asynchronous mode). If
 * onComplete is undefined, then we are in synchronous mode so return
 * SyncPromise.getValue(promise) which will throw an exception if the promise is
 * not a SyncPromise (or is a SyncPromise in the rejected state).
 * @param {function} onComplete If defined, this calls promise.then to fulfill
 * the promise, then calls onComplete(value) with the value of the promise.
 * If onComplete is undefined, the return value is described below.
 * NOTE: The library will log any exceptions thrown by this callback, but for
 * better error handling the callback should catch and properly handle any
 * exceptions.
 * @param {function} onError (optional) If defined, then onComplete must be
 * defined and if there is an error when this calls promise.then, this calls
 * onError(err) with the value of the error. If onComplete is undefined, then
 * onError is ignored and this will call SyncPromise.getValue(promise) which may
 * throw an exception.
 * NOTE: The library will log any exceptions thrown by this callback, but for
 * better error handling the callback should catch and properly handle any
 * exceptions.
 * @param {Promise|SyncPromise} promise If onComplete is defined, this calls
 * promise.then. Otherwise, this calls SyncPromise.getValue(promise).
 * @return {any} If onComplete is undefined, return SyncPromise.getValue(promise).
 * Otherwise, if onComplete is supplied then return undefined and use
 * onComplete as described above.
 * @throws Error If onComplete is undefined and promise is not a SyncPromise.
 * @throws any If onComplete is undefined and promise is a SyncPromise in the
 * rejected state.
 */
SyncPromise.complete = function(onComplete, onErrorOrPromise, promise)
{
  var onError;
  if (promise)
    onError = onErrorOrPromise;
  else {
    promise = onErrorOrPromise;
    onError = null;
  }

  if (onComplete)
    promise
    .then(function(value) {
      try {
        onComplete(value);
      } catch (ex) {
        console.log("Error in onComplete: " + NdnCommon.getErrorWithStackTrace(ex));
      }
    }, function(err) {
      if (onError) {
        try {
          onError(err);
        } catch (ex) {
          console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex));
        }
      }
      else {
        if (promise instanceof SyncPromise)
          throw err;
        else
          // We are in an async promise callback, so a thrown exception won't
          // reach the caller. Just log it.
          console.log("Uncaught exception from a Promise: " +
            NdnCommon.getErrorWithStackTrace(err));
      }
    });
  else
    return SyncPromise.getValue(promise);
};