promise comparisons

JavaScript performance comparison

Revision 112 of this test case created by rvmn

Info

This is a comparison of different promise libraries, performing the most basic tasks of creating a promise, adding a then handler and then resolving the promise.

Preparation code

<script src="//rawgithub.com/petkaantonov/bluebird/master/js/browser/bluebird.js"></script>
<script src="//rawgithub.com/calvinmetcalf/lie/1.3.0/dist/lie.noConflict.js"></script>
<script>
  window.BluebirdPromise = window.Promise.noConflict();
  delete window.Promise;
</script>
<script src="//rsvpjs-builds.s3.amazonaws.com/rsvp-latest.js"></script>
<script src="//rawgithub.com/calvinmetcalf/catiline/gh-pages/dist/catiline.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/q.js/0.9.6/q.min.js">
</script>
<script>
  window.define = function(factory) {
    try {
      delete window.define;
    } catch (e) {
      window.define = void 0;
    } // IE
    window.when = factory();
  };
  window.define.amd = {};
</script>
<script src="https://rawgithub.com/cujojs/when/master/when.js"></script>
<script src="http://s3.amazonaws.com/es6-promises/promise-0.1.0.min.js"></script>
<script>
// Pimp
/*
 * @author  Mudit Ameta
 * @license https://github.com/zeusdeux/pimp/blob/master/LICENSE MIT
 */

//# sourceMappingURL=pimp.min.map
!function(n){"use strict";function e(n){function r(n){var e;null!==s&&(n.done||(e=s?n.onFulfilled:n.onRejected,e?t(function(){var t;try{t=e(f),n.resolve(t)}catch(r){n.reject(r)}}):s?n.resolve(f):n.reject(f),n.done=!0))}function o(){for(var n in a)r(a[n])}function i(n){if(null===s){if(n)if(l===n)c(new TypeError("Promise cannot resolve to itself. Sorry brah!"));else if(n.constructor&&"Promise"===n.constructor.name)n.then(i,c);else if("function"==typeof n||"object"==typeof n)try{var e=n.then;"function"==typeof e?u(e.bind(n),i,c):(s=!0,f=n)}catch(t){c(t)}else s=!0,f=n;else s=!0,f=n;o()}}function c(n){null===s&&(s=!1,f=n,o())}function u(n,e,t){var r=!1;try{n(function(n){r||(r=!0,e(n))},function(n){r||(r=!0,t(n))})}catch(o){r||(r=!0,t(o))}}var f,s=null,a=[],l=this;if("function"!=typeof n)return new TypeError("Promise constructor requires a function");try{n(i,c)}catch(p){c(p)}this.then=function(n,t){var o,i=new e(function(e,r){o={promise:i,onFulfilled:"function"==typeof n&&n,onRejected:"function"==typeof t&&t,resolve:e,reject:r,done:!1}});return r(o),a.push(o),i},this.inspect=function(){var n={};return null===s?n.state="pending":s?(n.state="fulfilled",n.value=f):(n.state="rejected",n.reason=f),n}}n.exports?n.exports=e:n.Pimp=e;var t;try{process&&process.nextTick?t=process.nextTick:setImmediate&&(t=setImmediate)}catch(r){t=function(n){window.setTimeout(function(){n()},0)}}}("undefined"==typeof module?window:module),function(n){"use strict";function e(n,e){switch(n){case"Pimp.all":case"Pimp.allFail":case"Pimp.race":if(!(e instanceof Array))throw new SyntaxError(n+" needs to be passed an array");if(!e.length)throw new SyntaxError(n+" needs an array of length >= 1");break;case"Pimp.denodeify":if(!(e instanceof Function))throw new SyntaxError(n+" needs to be passed a function to promisify")}return!0}var t;t="[object Window]"===n.toString()&&"undefined"==typeof require?n.Pimp:require("./pimp"),n.exports?n.exports=t:n.Pimp=t,t.prototype.catch=function(n){return this.then(function(n){return n},n)},t.resolve=function(n){return new t(function(e){e(n)})},t.reject=function(n){return new t(function(e,t){t(n)})},t.cast=function(n){return n instanceof t&&n.then?n:t.resolve(n)},t.all=function(n){return e("Pimp.all",n),new t(function(e,r){var o=0,i=[];for(var c in n)n.hasOwnProperty(c)&&(n[c].then||(n[c]=t.cast(n[c])),n[c].then(function(t){o++,i.push(t),n.length===o&&e(i)},function(n){r(n)}))})},t.allFail=function(n){return e("Pimp.allFail",n),new t(function(e,r){var o=0,i=[];for(var c in n)n.hasOwnProperty(c)&&(n[c].then||(n[c]=t.cast(n[c])),n[c].then(function(n){r(n)},function(t){o++,i.push(t),n.length===o&&e(i)}))})},t.race=function(n){return e("Pimp.race",n),new t(function(e,r){for(var o in n)n.hasOwnProperty(o)&&(n[o].then||(n[o]=t.cast(n[o])),n[o].then(function(n){e(n)},function(n){r(n)}))})},t.deferred=function(){var n={};return n.promise=new t(function(e,t){n.resolve=e,n.reject=t}),n.inspect=n.promise.inspect,n},t.denodeify=function(n){return e("Pimp.denodeify",n),function(){var e=t.deferred(),r=function(n,t){n?e.reject(n):e.resolve(t)};return[].push.call(arguments,r),n.apply(this,arguments),e.promise}}}("undefined"==typeof module?window:module);
</script>
<script>
// Kew
function Promise(onSuccess, onFail) {
  this.promise = this
  this._isPromise = true
  this._successFn = onSuccess
  this._failFn = onFail
  this._scope = this
  this._boundArgs = null
  this._hasContext = false
  this._nextContext = undefined
  this._currentContext = undefined
}

/**
 * Keep track of the number of promises that are rejected along side
 * the number of rejected promises we call _failFn on so we can look
 * for leaked rejections.
 */

function PromiseStats() {
  this.errorsEmitted = 0
  this.errorsHandled = 0
}

var stats = new PromiseStats()

Promise.prototype._handleError = function () {
  if (!this._errorHandled) {
    stats.errorsHandled++
    this._errorHandled = true
  }
}

/**
 * Specify that the current promise should have a specified context
 * @param  {*} context context
 * @private
 */

Promise.prototype._useContext = function (context) {
  this._nextContext = this._currentContext = context
  this._hasContext = true
  return this
}

Promise.prototype.clearContext = function () {
  this._hasContext = false
  this._nextContext = undefined
  return this
}

/**
 * Set the context for all promise handlers to follow
 *
 * NOTE(dpup): This should be considered deprecated.  It does not do what most
 * people would expect.  The context will be passed as a second argument to all
 * subsequent callbacks.
 *
 * @param {*} context An arbitrary context
 */

Promise.prototype.setContext = function (context) {
  this._nextContext = context
  this._hasContext = true
  return this
}

/**
 * Get the context for a promise
 * @return {*} the context set by setContext
 */

Promise.prototype.getContext = function () {
  return this._nextContext
}

/**
 * Resolve this promise with a specified value
 *
 * @param {*=} data
 */

Promise.prototype.resolve = function (data) {
  if (this._error || this._hasData) throw new Error("Unable to resolve or reject the same promise twice")

  var i
  if (data && isPromise(data)) {
    this._child = data
    if (this._promises) {
      for (i = 0; i < this._promises.length; i += 1) {
        data._chainPromise(this._promises[i])
      }
      delete this._promises
    }

    if (this._onComplete) {
      for (i = 0; i < this._onComplete.length; i+= 1) {
        data.fin(this._onComplete[i])
      }
      delete this._onComplete
    }
  } else if (data && isPromiseLike(data)) {
    data.then(
      function(data) { this.resolve(data) }.bind(this),
      function(err) { this.reject(err) }.bind(this)
    )
  } else {
    this._hasData = true
    this._data = data

    if (this._onComplete) {
      for (i = 0; i < this._onComplete.length; i++) {
        this._onComplete[i]()
      }
    }

    if (this._promises) {
      for (i = 0; i < this._promises.length; i += 1) {
        this._promises[i]._withInput(data)
      }
      delete this._promises
    }
  }
}

/**
 * Reject this promise with an error
 *
 * @param {!Error} e
 */

Promise.prototype.reject = function (e) {
  if (this._error || this._hasData) throw new Error("Unable to resolve or reject the same promise twice")

  var i
  this._error = e
  stats.errorsEmitted++

  if (this._ended) {
    this._handleError()
    process.nextTick(function onPromiseThrow() {
      throw e
    })
  }

  if (this._onComplete) {
    for (i = 0; i < this._onComplete.length; i++) {
      this._onComplete[i]()
    }
  }

  if (this._promises) {
    this._handleError()
    for (i = 0; i < this._promises.length; i += 1) {
      this._promises[i]._withError(e)
    }
    delete this._promises
  }
}

/**
 * Provide a callback to be called whenever this promise successfully
 * resolves. Allows for an optional second callback to handle the failure
 * case.
 *
 * @param {?function(this:void, T, ?): RESULT|undefined} onSuccess
 * @param {?function(this:void, !Error, ?): RESULT=} onFail
 * @return {!Promise.<RESULT>} returns a new promise with the output of the onSuccess or
 *     onFail handler
 * @template RESULT
 */

Promise.prototype.then = function (onSuccess, onFail) {
  var promise = new Promise(onSuccess, onFail)
  if (this._nextContext) promise._useContext(this._nextContext)

  if (this._child) this._child._chainPromise(promise)
  else this._chainPromise(promise)

  return promise
}

/**
 * Provide a callback to be called whenever this promise successfully
 * resolves. The callback will be executed in the context of the provided scope.
 *
 * @param {function(this:SCOPE, T, ?): RESULT} onSuccess
 * @param {SCOPE} scope Object whose context callback will be executed in.
 * @param {...*} var_args Additional arguments to be passed to the promise callback.
 * @return {!Promise.<RESULT>} returns a new promise with the output of the onSuccess
 * @template SCOPE, RESULT
 */

Promise.prototype.thenBound = function (onSuccess, scope, var_args) {
  var promise = new Promise(onSuccess)
  if (this._nextContext) promise._useContext(this._nextContext)

  promise._scope = scope
  if (arguments.length > 2) {
    promise._boundArgs = Array.prototype.slice.call(arguments, 2)
  }

  // Chaining must happen after setting args and scope since it may fire callback.
  if (this._child) this._child._chainPromise(promise)
  else this._chainPromise(promise)

  return promise
}

/**
 * Provide a callback to be called whenever this promise is rejected
 *
 * @param {function(this:void, !Error, ?)} onFail
 * @return {!Promise.<T>} returns a new promise with the output of the onFail handler
 */

Promise.prototype.fail = function (onFail) {
  return this.then(null, onFail)
}

/**
 * Provide a callback to be called whenever this promise is rejected.
 * The callback will be executed in the context of the provided scope.
 *
 * @param {function(this:SCOPE, Error, ?)} onFail
 * @param {SCOPE} scope Object whose context callback will be executed in.
 * @param {...?} var_args
 * @return {!Promise.<T>} returns a new promise with the output of the onSuccess
 * @template SCOPE
 */

Promise.prototype.failBound = function (onFail, scope, var_args) {
  var promise = new Promise(null, onFail)
  if (this._nextContext) promise._useContext(this._nextContext)

  promise._scope = scope
  if (arguments.length > 2) {
    promise._boundArgs = Array.prototype.slice.call(arguments, 2)
  }

  // Chaining must happen after setting args and scope since it may fire callback.
  if (this._child) this._child._chainPromise(promise)
  else this._chainPromise(promise)

  return promise
}

/**
 * Provide a callback to be called whenever this promise is either resolved
 * or rejected.
 *
 * @param {function()} onComplete
 * @return {!Promise.<T>} returns the current promise
 */

Promise.prototype.fin = function (onComplete) {
  if (this._hasData || this._error) {
    onComplete()
    return this
  }

  if (this._child) {
    this._child.fin(onComplete)
  } else {
    if (!this._onComplete) this._onComplete = [onComplete]
    else this._onComplete.push(onComplete)
  }

  return this
}

/**
 * Mark this promise as "ended". If the promise is rejected, this will throw an
 * error in whatever scope it happens to be in
 *
 * @return {!Promise.<T>} returns the current promise
 * @deprecated Prefer done(), because it's consistent with Q.
 */

Promise.prototype.end = function () {
  this._end()
  return this
}


/**
 * Mark this promise as "ended".
 * @private
 */

Promise.prototype._end = function () {
  if (this._error) {
    this._handleError()
    throw this._error
  }
  this._ended = true
  return this
}

/**
 * Close the promise. Any errors after this completes will be thrown to the global handler.
 *
 * @param {?function(this:void, T, ?)=} onSuccess a function to handle successful
 *     resolution of this promise
 * @param {?function(this:void, !Error, ?)=} onFailure a function to handle failed
 *     resolution of this promise
 * @return {void}
 */

Promise.prototype.done = function (onSuccess, onFailure) {
  var self = this
  if (onSuccess || onFailure) {
    self = self.then(onSuccess, onFailure)
  }
  self._end()
}

/**
 * Return a new promise that behaves the same as the current promise except
 * that it will be rejected if the current promise does not get fulfilled
 * after a certain amount of time.
 *
 * @param {number} timeoutMs The timeout threshold in msec
 * @param {string=} timeoutMsg error message
 * @return {!Promise.<T>} a new promise with timeout
 */

 Promise.prototype.timeout = function (timeoutMs, timeoutMsg) {
  var deferred = new Promise()
  var isTimeout = false

  var timeout = setTimeout(function() {
    deferred.reject(new Error(timeoutMsg || 'Promise timeout after ' + timeoutMs + ' ms.'))
    isTimeout = true
  }, timeoutMs)

  this.then(function (data) {
    if (!isTimeout) {
      clearTimeout(timeout)
      deferred.resolve(data)
    }
  },
  function (err) {
    if (!isTimeout) {
      clearTimeout(timeout)
      deferred.reject(err)
    }
  })

  return deferred.promise
}

/**
 * Attempt to resolve this promise with the specified input
 *
 * @param {*} data the input
 */

Promise.prototype._withInput = function (data) {
  if (this._successFn) {
    try {
      this.resolve(this._call(this._successFn, [data, this._currentContext]))
    } catch (e) {
      this.reject(e)
    }
  } else this.resolve(data)

  // context is no longer needed
  delete this._currentContext
}

/**
 * Attempt to reject this promise with the specified error
 *
 * @param {!Error} e
 * @private
 */

Promise.prototype._withError = function (e) {
  if (this._failFn) {
    try {
      this.resolve(this._call(this._failFn, [e, this._currentContext]))
    } catch (thrown) {
      this.reject(thrown)
    }
  } else this.reject(e)

  // context is no longer needed
  delete this._currentContext
}

/**
 * Calls a function in the correct scope, and includes bound arguments.
 * @param {Function} fn
 * @param {Array} args
 * @return {*}
 * @private
 */

Promise.prototype._call = function (fn, args) {
  if (this._boundArgs) {
    args = this._boundArgs.concat(args)
  }
  return fn.apply(this._scope, args)
}

/**
 * Chain a promise to the current promise
 *
 * @param {!Promise} promise the promise to chain
 * @private
 */

Promise.prototype._chainPromise = function (promise) {
  var i
  if (this._hasContext) promise._useContext(this._nextContext)

  if (this._child) {
    this._child._chainPromise(promise)
  } else if (this._hasData) {
    promise._withInput(this._data)
  } else if (this._error) {
    // We can't rely on _withError() because it's called on the chained promises
    // and we need to use the source's _errorHandled state
    this._handleError()
    promise._withError(this._error)
  } else if (!this._promises) {
    this._promises = [promise]
  } else {
    this._promises.push(promise)
  }
}

/**
 * Utility function used for creating a node-style resolver
 * for deferreds
 *
 * @param {!Promise} deferred a promise that looks like a deferred
 * @param {Error=} err an optional error
 * @param {*=} data optional data
 */

function resolver(deferred, err, data) {
  if (err) deferred.reject(err)
  else deferred.resolve(data)
}

/**
 * Creates a node-style resolver for a deferred by wrapping
 * resolver()
 *
 * @return {function(?Error, *)} node-style callback
 */

Promise.prototype.makeNodeResolver = function () {
  return resolver.bind(null, this)
}

/**
 * Return true iff the given object is a promise of this library.
 *
 * Because kew's API is slightly different than other promise libraries,
 * it's important that we have a test for its promise type. If you want
 * to test for a more general A+ promise, you should do a cap test for
 * the features you want.
 *
 * @param {*} obj The object to test
 * @return {boolean} Whether the object is a promise
 */

function isPromise(obj) {
  return !!obj._isPromise
}

/**
 * Return true iff the given object is a promise-like object, e.g. appears to
 * implement Promises/A+ specification
 *
 * @param {*} obj The object to test
 * @return {boolean} Whether the object is a promise-like object
 */

function isPromiseLike(obj) {
  return typeof obj === 'object' && typeof obj.then === 'function'
}

/**
 * Static function which creates and resolves a promise immediately
 *
 * @param {T} data data to resolve the promise with
 * @return {!Promise.<T>}
 * @template T
 */

function resolve(data) {
  var promise = new Promise()
  promise.resolve(data)
  return promise
}

/**
 * Static function which creates and rejects a promise immediately
 *
 * @param {!Error} e error to reject the promise with
 * @return {!Promise}
 */

function reject(e) {
  var promise = new Promise()
  promise.reject(e)
  return promise
}

/**
 * Replace an element in an array with a new value. Used by .all() to
 * call from .then()
 *
 * @param {!Array} arr
 * @param {number} idx
 * @param {*} val
 * @return {*} the val that's being injected into the array
 */

function replaceEl(arr, idx, val) {
  arr[idx] = val
  return val
}

/**
 * Replace an element in an array as it is resolved with its value.
 * Used by .allSettled().
 *
 * @param {!Array} arr
 * @param {number} idx
 * @param {*} value The value from a resolved promise.
 * @return {*} the data that's being passed in
 */

function replaceElFulfilled(arr, idx, value) {
  arr[idx] = {
    state: 'fulfilled',
    value: value
  }
  return value
}

/**
 * Replace an element in an array as it is rejected with the reason.
 * Used by .allSettled().
 *
 * @param {!Array} arr
 * @param {number} idx
 * @param {*} reason The reason why the original promise is rejected
 * @return {*} the data that's being passed in
 */

function replaceElRejected(arr, idx, reason) {
  arr[idx] = {
    state: 'rejected',
    reason: reason
  }
  return reason
}

/**
 * Takes in an array of promises or literals and returns a promise which returns
 * an array of values when all have resolved. If any fail, the promise fails.
 *
 * @param {!Array.<!Promise>} promises
 * @return {!Promise.<!Array>}
 */

function all(promises) {
  if (arguments.length != 1 || !Array.isArray(promises)) {
    promises = Array.prototype.slice.call(arguments, 0)
  }
  if (!promises.length) return resolve([])

  var outputs = []
  var finished = false
  var promise = new Promise()
  var counter = promises.length

  for (var i = 0; i < promises.length; i += 1) {
    if (!promises[i] || !isPromiseLike(promises[i])) {
      outputs[i] = promises[i]
      counter -= 1
    } else {
      promises[i].then(replaceEl.bind(null, outputs, i))
      .then(function decrementAllCounter() {
        counter--
        if (!finished && counter === 0) {
          finished = true
          promise.resolve(outputs)
        }
      }, function onAllError(e) {
        if (!finished) {
          finished = true
          promise.reject(e)
        }
      })
    }
  }

  if (counter === 0 && !finished) {
    finished = true
    promise.resolve(outputs)
  }

  return promise
}

/**
 * Takes in an array of promises or values and returns a promise that is
 * fulfilled with an array of state objects when all have resolved or
 * rejected. If a promise is resolved, its corresponding state object is
 * {state: 'fulfilled', value: Object}; whereas if a promise is rejected, its
 * corresponding state object is {state: 'rejected', reason: Object}.
 *
 * @param {!Array} promises or values
 * @return {!Promise.<!Array>} Promise fulfilled with state objects for each input
 */

function allSettled(promises) {
  if (!Array.isArray(promises)) {
    throw Error('The input to "allSettled()" should be an array of Promise or values')
  }
  if (!promises.length) return resolve([])

  var outputs = []
  var promise = new Promise()
  var counter = promises.length

  for (var i = 0; i < promises.length; i += 1) {
    if (!promises[i] || !isPromiseLike(promises[i])) {
      replaceElFulfilled(outputs, i, promises[i])
      if ((--counter) === 0) promise.resolve(outputs)
    } else {
      promises[i]
        .then(replaceElFulfilled.bind(null, outputs, i), replaceElRejected.bind(null, outputs, i))
        .then(function () {
          if ((--counter) === 0) promise.resolve(outputs)
        })
    }
  }

  return promise
}

/**
 * Create a new Promise which looks like a deferred
 *
 * @return {!Promise}
 */

function defer() {
  return new Promise()
}

/**
 * Return a promise which will wait a specified number of ms to resolve
 *
 * @param {*} delayMsOrVal A delay (in ms) if this takes one argument, or ther
 *     return value if it takes two.
 * @param {number=} opt_delayMs
 * @return {!Promise}
 */

function delay(delayMsOrVal, opt_delayMs) {
  var returnVal = undefined
  var delayMs = delayMsOrVal
  if (typeof opt_delayMs != 'undefined') {
    delayMs = opt_delayMs
    returnVal = delayMsOrVal
  }

  if (typeof delayMs != 'number') {
    throw new Error('Bad delay value ' + delayMs)
  }

  var defer = new Promise()
  setTimeout(function onDelay() {
    defer.resolve(returnVal)
  }, delayMs)
  return defer
}

/**
 * Returns a promise that has the same result as `this`, but fulfilled
 * after at least ms milliseconds
 * @param {number} ms
 */

Promise.prototype.delay = function (ms) {
  return this.then(function (val) {
    return delay(val, ms)
  })
}

/**
 * Return a promise which will evaluate the function fn in a future turn with
 * the provided args
 *
 * @param {function(...)} fn
 * @param {...*} var_args a variable number of arguments
 * @return {!Promise}
 */

function fcall(fn, var_args) {
  var rootArgs = Array.prototype.slice.call(arguments, 1)
  var defer = new Promise()
  process.nextTick(function onNextTick() {
    try {
      defer.resolve(fn.apply(undefined, rootArgs))
    } catch (e) {
      defer.reject(e)
    }
  })
  return defer
}


/**
 * Returns a promise that will be invoked with the result of a node style
 * callback. All args to fn should be given except for the final callback arg
 *
 * @param {function(...)} fn
 * @param {...*} var_args a variable number of arguments
 * @return {!Promise}
 */

function nfcall(fn, var_args) {
  // Insert an undefined argument for scope and let bindPromise() do the work.
  var args = Array.prototype.slice.call(arguments, 0)
  args.splice(1, 0, undefined)
  return bindPromise.apply(undefined, args)()
}


/**
 * Binds a function to a scope with an optional number of curried arguments. Attaches
 * a node style callback as the last argument and returns a promise
 *
 * @param {function(...)} fn
 * @param {Object} scope
 * @param {...*} var_args a variable number of arguments
 * @return {function(...)}: !Promise}
 */

function bindPromise(fn, scope, var_args) {
  var rootArgs = Array.prototype.slice.call(arguments, 2)
  return function onBoundPromise(var_args) {
    var defer = new Promise()
    try {
      fn.apply(scope, rootArgs.concat(Array.prototype.slice.call(arguments, 0), defer.makeNodeResolver()))
    } catch (e) {
      defer.reject(e)
    }
    return defer
  }
}
K = {
    all: all
  , bindPromise: bindPromise
  , defer: defer
  , delay: delay
  , fcall: fcall
  , isPromise: isPromise
  , isPromiseLike: isPromiseLike
  , nfcall: nfcall
  , resolve: resolve
  , reject: reject
  , stats: stats
  , allSettled: allSettled
  , Promise: Promise
};
</script>
<script>
(function UMD(name,context,definition){
        // special form of UMD for polyfilling across evironments
        context[name] = context[name] || definition();
        if (typeof module != "undefined" && module.exports) { module.exports = context[name]; }
        else if (typeof define == "function" && define.amd) { define(function $AMD$(){ return context[name]; }); }
})("PromiseNPO",typeof global != "undefined" ? global : this,function DEF(){
        /*jshint validthis:true */
        "use strict";

        var cycle, scheduling_queue, ToString = Object.prototype.toString,
                timer = (typeof setImmediate != "undefined") ?
                        function timer(fn) { return setImmediate(fn); } :
                        setTimeout,
                builtInProp = Object.defineProperty ?
                        function builtInProp(obj,name,val,config) {
                                return Object.defineProperty(obj,name,{
                                        value: val,
                                        writable: true,
                                        configurable: config !== false
                                });
                        } :
                        function builtInProp(obj,name,val) {
                                obj[name] = val;
                                return obj;
                        }
        ;

        // Note: using a queue instead of array for efficiency
        scheduling_queue = (function Queue() {
                var first, last, item;

                function Item(fn,self) {
                        this.fn = fn;
                        this.self = self;
                        this.next = void 0;
                }

                return {
                        add: function add(fn,self) {
                                item = new Item(fn,self);
                                if (last) {
                                        last.next = item;
                                }
                                else {
                                        first = item;
                                }
                                last = item;
                                item = void 0;
                        },
                        drain: function drain() {
                                var f = first;
                                first = last = cycle = void 0;

                                while (f) {
                                        f.fn.call(f.self);
                                        f = f.next;
                                }
                        }
                };
        })();

        function schedule(fn,self) {
                scheduling_queue.add(fn,self);
                if (!cycle) {
                        cycle = timer(scheduling_queue.drain);
                }
        }

        // promise duck typing
        function isThenable(o) {
                var _then, o_type = typeof o;

                if (o != null &&
                        (
                                o_type == "object" || o_type == "function"
                        )
                ) {
                        _then = o.then;
                }
                return typeof _then == "function" ? _then : false;
        }

        function notify() {
                for (var i=0; i<this.chain.length; i++) {
                        notifyIsolated(
                                this,
                                (this.state === 1) ? this.chain[i].success : this.chain[i].failure,
                                this.chain[i]
                        );
                }
                this.chain.length = 0;
        }

        // NOTE: This is a separate function to isolate
        // the `try..catch` so that other code can be
        // optimized better
        function notifyIsolated(self,cb,chain) {
                var ret, _then;
                try {
                        if (cb === false) {
                                chain.reject(self.msg);
                        }
                        else {
                                if (cb === true) {
                                        ret = self.msg;
                                }
                                else {
                                        ret = cb.call(void 0,self.msg);
                                }

                                if (ret === chain.promise) {
                                        chain.reject(TypeError("Promise-chain cycle"));
                                }
                                else if (_then = isThenable(ret)) {
                                        _then.call(ret,chain.resolve,chain.reject);
                                }
                                else {
                                        chain.resolve(ret);
                                }
                        }
                }
                catch (err) {
                        chain.reject(err);
                }
        }

        function resolve(msg) {
                var _then, def_wrapper, self = this;

                // already triggered?
                if (self.triggered) { return; }

                self.triggered = true;

                // unwrap
                if (self.def) {
                        self = self.def;
                }

                try {
                        if (_then = isThenable(msg)) {
                                def_wrapper = new MakeDefWrapper(self);
                                _then.call(msg,
                                        function $resolve$(){ resolve.apply(def_wrapper,arguments); },
                                        function $reject$(){ reject.apply(def_wrapper,arguments); }
                                );
                        }
                        else {
                                self.msg = msg;
                                self.state = 1;
                                if (self.chain.length > 0) {
                                        schedule(notify,self);
                                }
                        }
                }
                catch (err) {
                        reject.call(def_wrapper || (new MakeDefWrapper(self)),err);
                }
        }

        function reject(msg) {
                var self = this;

                // already triggered?
                if (self.triggered) { return; }

                self.triggered = true;

                // unwrap
                if (self.def) {
                        self = self.def;
                }

                self.msg = msg;
                self.state = 2;
                if (self.chain.length > 0) {
                        schedule(notify,self);
                }
        }

        function iteratePromises(Constructor,arr,resolver,rejecter) {
                for (var idx=0; idx<arr.length; idx++) {
                        (function IIFE(idx){
                                Constructor.resolve(arr[idx])
                                .then(
                                        function $resolver$(msg){
                                                resolver(idx,msg);
                                        },
                                        rejecter
                                );
                        })(idx);
                }
        }

        function MakeDefWrapper(self) {
                this.def = self;
                this.triggered = false;
        }

        function MakeDef(self) {
                this.promise = self;
                this.state = 0;
                this.triggered = false;
                this.chain = [];
                this.msg = void 0;
        }

        function Promise(executor) {
                if (typeof executor != "function") {
                        throw TypeError("Not a function");
                }

                if (this.__NPO__ !== 0) {
                        throw TypeError("Not a promise");
                }

                // instance shadowing the inherited "brand"
                // to signal an already "initialized" promise
                this.__NPO__ = 1;

                var def = new MakeDef(this);

                this["then"] = function then(success,failure) {
                        var o = {
                                success: typeof success == "function" ? success : true,
                                failure: typeof failure == "function" ? failure : false
                        };
                        // Note: `then(..)` itself can be borrowed to be used against
                        // a different promise constructor for making the chained promise,
                        // by substituting a different `this` binding.
                        o.promise = new this.constructor(function extractChain(resolve,reject) {
                                if (typeof resolve != "function" || typeof reject != "function") {
                                        throw TypeError("Not a function");
                                }

                                o.resolve = resolve;
                                o.reject = reject;
                        });
                        def.chain.push(o);

                        if (def.state !== 0) {
                                schedule(notify,def);
                        }

                        return o.promise;
                };
                this["catch"] = function $catch$(failure) {
                        return this.then(void 0,failure);
                };

                try {
                        executor.call(
                                void 0,
                                function publicResolve(msg){
                                        resolve.call(def,msg);
                                },
                                function publicReject(msg) {
                                        reject.call(def,msg);
                                }
                        );
                }
                catch (err) {
                        reject.call(def,err);
                }
        }

        var PromisePrototype = builtInProp({},"constructor",Promise,
                /*configurable=*/false
        );

        builtInProp(
                Promise,"prototype",PromisePrototype,
                /*configurable=*/false
        );

        // built-in "brand" to signal an "uninitialized" promise
        builtInProp(PromisePrototype,"__NPO__",0,
                /*configurable=*/false
        );

        builtInProp(Promise,"resolve",function Promise$resolve(msg) {
                var Constructor = this;

                // spec mandated checks
                // note: best "isPromise" check that's practical for now
                if (msg && typeof msg == "object" && msg.__NPO__ === 1) {
                        return msg;
                }

                return new Constructor(function executor(resolve,reject){
                        if (typeof resolve != "function" || typeof reject != "function") {
                                throw TypeError("Not a function");
                        }

                        resolve(msg);
                });
        });

        builtInProp(Promise,"reject",function Promise$reject(msg) {
                return new this(function executor(resolve,reject){
                        if (typeof resolve != "function" || typeof reject != "function") {
                                throw TypeError("Not a function");
                        }

                        reject(msg);
                });
        });

        builtInProp(Promise,"all",function Promise$all(arr) {
                var Constructor = this;

                // spec mandated checks
                if (ToString.call(arr) != "[object Array]") {
                        return Constructor.reject(TypeError("Not an array"));
                }
                if (arr.length === 0) {
                        return Constructor.resolve([]);
                }

                return new Constructor(function executor(resolve,reject){
                        if (typeof resolve != "function" || typeof reject != "function") {
                                throw TypeError("Not a function");
                        }

                        var len = arr.length, msgs = Array(len), count = 0;

                        iteratePromises(Constructor,arr,function resolver(idx,msg) {
                                msgs[idx] = msg;
                                if (++count === len) {
                                        resolve(msgs);
                                }
                        },reject);
                });
        });

        builtInProp(Promise,"race",function Promise$race(arr) {
                var Constructor = this;

                // spec mandated checks
                if (ToString.call(arr) != "[object Array]") {
                        return Constructor.reject(TypeError("Not an array"));
                }

                return new Constructor(function executor(resolve,reject){
                        if (typeof resolve != "function" || typeof reject != "function") {
                                throw TypeError("Not a function");
                        }

                        iteratePromises(Constructor,arr,function resolver(idx,msg){
                                resolve(msg);
                        },reject);
                });
        });

        return Promise;
});
</script>
<script src="//rawgit.com/cujojs/when/master/when.js"></script>

Preparation code output

Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in unknown unknown
Test Ops/sec
lie
// async test
var d = lie();
var code = 'lie' + Math.random();

function eventFunc(e) {
  if (e.data === code) {
    window.removeEventListener('message', eventFunc);
    d.resolve();
  }
}
window.addEventListener('message', eventFunc);
d.promise.then(function() {
  deferred.resolve()
})
window.postMessage(code, '*');
pending…
when
var x = 1;
var d = when.defer()
var code = 'when' + Math.random();

function eventFunc(e) {
  if (e.data === code) {
    window.removeEventListener('message', eventFunc);
    d.resolve();

  }
}
window.addEventListener('message', eventFunc);
d.promise.then(function() {
  deferred.resolve()
})
window.postMessage(code, '*');
pending…
RSVP
// async test
var d = RSVP.defer();
var code = 'rsvp' + Math.random();

function eventFunc(e) {
  if (e.data === code) {
    window.removeEventListener('message', eventFunc);
    d.resolve();

  }
}
window.addEventListener('message', eventFunc);
d.promise.then(function() {
  deferred.resolve()
})
window.postMessage(code, '*');
pending…
q
// async test
var d = Q.defer()
var code = 'q' + Math.random();

function eventFunc(e) {
  if (e.data === code) {
    window.removeEventListener('message', eventFunc);
    d.resolve();

  }
}
window.addEventListener('message', eventFunc);
d.promise.then(function() {
  deferred.resolve()
})
window.postMessage(code, '*');
pending…
catiline
// async test
var d = cw.deferred()
var code = 'cw' + Math.random();

function eventFunc(e) {
  if (e.data === code) {
    window.removeEventListener('message', eventFunc);
    d.resolve();

  }
}
window.addEventListener('message', eventFunc);
d.promise.then(function() {
  deferred.resolve()
})
window.postMessage(code, '*');
pending…
bluebird
// async test
var d = BluebirdPromise.pending();
var code = 'pend' + Math.random();

function eventFunc(e) {
  if (e.data === code) {
    window.removeEventListener('message', eventFunc);
    d.fulfill();

  }
}

d.promise.then(function() {
  deferred.resolve()
});
window.addEventListener('message', eventFunc);
window.postMessage(code, '*');
pending…
Pimp
// async test
var d = Pimp.deferred()
var code = 'pimp' + Math.random();

function eventFunc(e) {
  if (e.data === code) {
    window.removeEventListener('message', eventFunc);
    d.resolve();

  }
}
window.addEventListener('message', eventFunc);
d.promise.then(function() {
  deferred.resolve()
})
window.postMessage(code, '*');
pending…
ES6 Promise polyfill
// async test
var d = Promise()
var code = 'es6' + Math.random();

function eventFunc(e) {
  if (e.data === code) {
    window.removeEventListener('message', eventFunc);
    d.resolve();

  }
}
window.addEventListener('message', eventFunc);
d.promise.then(function() {
  deferred.resolve()
})
window.postMessage(code, '*');
pending…
Kew
// async test
var d = K.defer()
var code = 'q' + Math.random();

function eventFunc(e) {
  if (e.data === code) {
    window.removeEventListener('message', eventFunc);
    d.resolve();

  }
}
window.addEventListener('message', eventFunc);
d.promise.then(function() {
  deferred.resolve()
})
window.postMessage(code, '*');
pending…

Compare results of other browsers

Revisions

You can edit these tests or add even more tests to this page by appending /edit to the URL. Here’s a list of current revisions for this page:

0 comments

Add a comment