jsPerf.app is an online JavaScript performance benchmark test runner & jsperf.com mirror. It is a complete rewrite in homage to the once excellent jsperf.com now with hopefully a more modern & maintainable codebase.
jsperf.com URLs are mirrored at the same path, e.g:
https://jsperf.com/negative-modulo/2
Can be accessed at:
https://jsperf.app/negative-modulo/2
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.
Recent benchmarks deliberately cripple bluebird but didn't force the same method on other libraries.
*Added ES6 Promise polyfill *Added kew *Added MyDeferred
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="https://rawgithub.com/cho45/jsdeferred/master/jsdeferred.js"></script>
<script src="https://rawgithub.com/petkaantonov/bluebird/master/js/browser/bluebird.js"></script>
<script src="https://rawgithub.com/calvinmetcalf/lie/master/dist/lie.noConflict.js"></script><script src="http://rsvpjs-builds.s3.amazonaws.com/rsvp-latest.js"></script>
<script src="https://rawgithub.com/calvinmetcalf/catiline/master/dist/catiline.js"></script>
<script src="https://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>
var worker = cw({
init:function(self){
self.on('ping',function(d){
self.fire('pong',d);
});
}
});
</script>
<script src="http://yui.yahooapis.com/3.14.1/build/yui/yui-min.js"></script>
<script>
YUI().use('promise', function (Y) {
window.Y = Y;
});
</script>
<script src="http://s3.amazonaws.com/es6-promises/promise-0.1.1.min.js"></script>
<script>
window.kew = (function(){
/** @typedef {function(?, ?)} */
var OnSuccessCallbackType;
/** @typedef {function(!Error, ?)} */
var OnFailCallbackType;
/**
* An object representing a "promise" for a future value
*
* @param {?OnSuccessCallbackType=} onSuccess a function to handle successful
* resolution of this promise
* @param {OnFailCallbackType=} onFail a function to handle failed
* resolution of this promise
* @constructor
*/
function Promise(onSuccess, onFail) {
this.promise = this
this._isPromise = true
this._successFn = onSuccess
this._failFn = onFail
this._hasContext = false
this._nextContext = undefined
this._currentContext = undefined
}
/**
* 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
* @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
if (this._ended) {
process.nextTick(function onPromiseThrow() {
throw e
})
}
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]._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 {?OnSuccessCallbackType} onSuccess
* @param {OnFailCallbackType=} onFail
* @return {!Promise} returns a new promise with the output of the onSuccess or
* onFail handler
*/
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 is rejected
*
* @param {OnFailCallbackType} onFail
* @return {!Promise} 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 either resolved
* or rejected.
*
* @param {function()} onComplete
* @return {!Promise} 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} returns the current promise
*/
Promise.prototype.end = function () {
if (this._error) {
throw this._error
}
this._ended = true
return this
}
/**
* 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
* @returns 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._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._failFn(e, this._currentContext))
} catch (thrown) {
this.reject(thrown)
}
} else this.reject(e)
// context is no longer needed
delete this._currentContext
}
/**
* 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) {
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 {*} data data to resolve the promise with
* @return {!Promise}
*/
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}
*/
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 literals and returns a promise which returns
* an array of values when all have resolved or rejected.
*
* @param {!Array.<!Promise>} promises
* @return {!Array.<Object>} The state of the promises. 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}
*/
function allSettled(promises) {
if (!Array.isArray(promises)) {
throw Error('The input to "allSettled()" should be an array of Promise')
}
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 {number} delayMs
* @param {*} returnVal
* @return {!Promise} returns returnVal
*/
function delay(delayMs, returnVal) {
var defer = new Promise()
setTimeout(function onDelay() {
defer.resolve(returnVal)
}, delayMs)
return defer
}
/**
* 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() {
defer.resolve(fn.apply(undefined, rootArgs))
})
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()
fn.apply(scope, rootArgs.concat(Array.prototype.slice.call(arguments, 0), defer.makeNodeResolver()))
return defer
}
}
var module = {};
module.exports = {
all: all
, bindPromise: bindPromise
, defer: defer
, delay: delay
, fcall: fcall
, isPromise: isPromise
, isPromiseLike: isPromiseLike
, nfcall: nfcall
, resolve: resolve
, reject: reject
, allSettled: allSettled
}
return module.exports;
}())
/**
* @author RubaXa <trash@rubaxa.org>
* @license MIT
*/
;(function (){
function _then(promise, method, callback){
return function (){
var args = arguments;
if( typeof callback === 'function' ){
var retVal = callback.apply(promise, args);
if( retVal && typeof retVal.then === 'function' ){
retVal.done(promise.resolve).fail(promise.reject);
return;
}
}
promise[method].apply(promise, args);
};
}
/**
* Fastest Deferred.
* @returns {Deferred}
*/
var Deferred = function (){
var
_args,
_doneFn = [],
_failFn = [],
dfd = {
done: function (fn){
_doneFn.push(fn);
return dfd;
},
fail: function (fn){
_failFn.push(fn);
return dfd;
},
then: function (doneFn, failFn){
var promise = Deferred();
dfd
.done(_then(promise, 'resolve', doneFn))
.fail(_then(promise, 'reject', failFn))
;
return promise;
},
always: function (fn){
return dfd.done(fn).fail(fn);
},
resolve: _setState(true),
reject: _setState(false)
}
;
function _setState(state){
return function (){
_args = arguments;
dfd.done =
dfd.fail =
dfd.resolve =
dfd.reject = function (){
return dfd;
};
dfd[state ? 'done' : 'fail'] = function (fn){
if( typeof fn === 'function' ){
fn.apply(dfd, _args);
}
return dfd;
};
var
fn
, fns = state ? _doneFn : _failFn
, i = 0, n = fns.length
;
for( ; i < n; i++ ){
fn = fns[i];
if( typeof fn === 'function' ){
fn.apply(dfd, _args);
}
}
fns = _doneFn = _failFn = null;
return dfd;
}
}
return dfd;
};
/**
* @param {Array} args
* @returns {defer|*}
*/
Deferred.when = function (args){
var
dfd = Deferred()
, d
, i = args.length
, remain = i || 1
, _doneFn = function (){
if( --remain === 0 ){
dfd.resolve();
}
}
;
if( i === 0 ){
_doneFn();
}
else {
while( i-- ){
d = args[i];
if( d && d.then ){
d.then(_doneFn, dfd.reject);
}
}
}
return dfd;
};
// exports
window.MyDeferred = Deferred;
})();
window.MyPromise = function () {
var global = Function('return this')();
var isObject = function (arg) {
return typeof arg === 'object' &&
arg !== null;
};
var isFunction = function (arg) {
return typeof arg === 'function';
};
var isObjectOrFunction = function (arg) {
return isObject(arg) || isFunction(arg);
};
var isNative = function (target) {
return typeof target === 'object' ||
(target + '').slice(-17) === '{ [native code] }';
};
var final_ = function (initialiser, value) {
return function () {
if (initialiser !== null) {
value = initialiser(value);
initialiser = null;
}
return value;
};
};
var scheduleMicrotask = function () {
var finalSchedule = function () {
var queue = [];
function flushQueue() {
var tasks = queue;
queue = [];
for (var i = 0; i < tasks.length; i++) {
tasks[i]();
}
}
function usePromise() {
return null;
if (!isNative(global.Promise)) {
return null;
}
var fulfilled = new Promise(function (resolve) {
resolve();
});
return function () {
fulfilled.then(flushQueue);
};
}
function useMutationObserver() {
if (!isNative(global.MutationObserver)) {
return null;
}
var iterations = 0;
var observer = new MutationObserver(flushQueue);
var node = document.createTextNode('');
observer.observe(node, { characterData: true });
return function() {
node.data = (iterations = ++iterations % 2);
};
}
function useSetImmediate() {
if (!isNative(global.setImmediate)) {
return null;
}
var immediateId;
var timerId;
function raceFlush() {
clearTimeout(timerId);
clearImmediate(immediateId);
flushQueue();
}
return function () {
immediateId = setImmediate(raceFlush);
timerId = setTimeout(raceFlush);
};
}
function useSetTimeout() {
return function () {
setTimeout(flushQueue);
};
}
var scheduleFlush = usePromise() ||
useMutationObserver() ||
useSetImmediate() ||
useSetTimeout();
return function (callback) {
queue.push(callback);
if (queue.length <= 1) {
scheduleFlush();
}
};
}();
return function (callback) {
finalSchedule(callback);
};
}();
return function () {
var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;
function Promise(resolver) {
this._state = PENDING;
this._value = [];
var promise = this;
try {
resolver(function (value) {
resolve(promise, value);
}, function (reason) {
reject(promise, reason);
});
} catch (ex) {
reject(promise, ex);
}
}
function Promise2() {
this._state = PENDING;
this._value = [];
}
Promise2.prototype = Promise.prototype;
Promise.prototype._state = PENDING;
Promise.prototype._value = '';
function invokeCallback(settled, value, callback, promise) {
if (isFunction(callback)) {
try {
resolve(promise, callback(value));
} catch (ex) {
reject(promise, ex);
}
} else {
publish(promise, settled, value);
}
}
function publish(promise, settled, value) {
var subscribers = promise._value;
promise._state = settled;
promise._value = value;
for (var i = 0; i < subscribers.length; i += 3) {
invokeCallback(
settled,
value,
subscribers[i + settled],
subscribers[i]);
}
}
function schedulePublish(promise, state, value) {
scheduleMicrotask(function () {
publish(promise, state, value);
});
}
function isThenable(value) {
return isObjectOrFunction(value) &&
isFunction(value.then);
}
function chainThenable(thenable, promise) {
try {
thenable.then(function (value) {
resolve(promise, value);
}, function (reason) {
reject(promise, reason);
});
} catch (ex) {
reject(promise, ex);
}
}
function resolve(promise, value) {
if (value !== this && isThenable(value)) {
chainThenable(value, promise);
} else {
schedulePublish(promise, FULFILLED, value);
}
}
function reject(promise, reason) {
schedulePublish(promise, REJECTED, reason);
}
Promise.prototype.then = function (onFulfilled, onRejected) {
var childPromise = new Promise2();
if (this._state <= PENDING) {
this._value.push(childPromise);
this._value.push(onFulfilled);
this._value.push(onRejected);
} else {
var callback = (this._state >= REJECTED) ?
onRejected : onFulfilled;
if (!isFunction(callback)) {
publish(childPromise, this._state, this._value);
} else {
var promise = this;
scheduleMicrotask(function () {
invokeCallback(
promise._state,
promise._value,
callback,
childPromise);
});
}
}
return childPromise;
};
window.MyPromise2 = Promise2;
window.resolveMyPromise = resolve;
return Promise;
}();
}();
</script>
Ready to run.
Test | Ops/sec | |
---|---|---|
lie |
| ready |
RSVP |
| ready |
q |
| ready |
catiline |
| ready |
bluebird |
| ready |
yui |
| ready |
JQuery |
| ready |
ES6 Promise polyfill |
| ready |
YUI + ES6 Promise polyfill |
| ready |
kew |
| ready |
MyDeferred |
| ready |
MyPromise |
| ready |
You can edit these tests or add more tests to this page by appending /edit to the URL.