From bab8deafd2d5ae26a5a11c3e6794df97039d7524 Mon Sep 17 00:00:00 2001 From: Jeff Cross Date: Wed, 30 Jul 2014 16:08:20 -0700 Subject: [PATCH 1/3] perf($q): move Deferred and Promise methods to prototypes NOTE: Deferred doesn't get all the advantages of moving methods to the prototype, since the constructor binds instance methods to "this" to support unbounded execution. NOTE: This change increases the gzipped version by 100 bytes. --- src/ng/q.js | 239 ++++++++++++++++++++++++++-------------------------- 1 file changed, 120 insertions(+), 119 deletions(-) diff --git a/src/ng/q.js b/src/ng/q.js index e571636f4d64..2630186662d6 100644 --- a/src/ng/q.js +++ b/src/ng/q.js @@ -247,143 +247,144 @@ function qFactory(nextTick, exceptionHandler) { * @returns {Deferred} Returns a new instance of deferred. */ var defer = function() { - var pending = [], - value, deferred; - - deferred = { - - resolve: function(val) { - if (pending) { - var callbacks = pending; - pending = undefined; - value = ref(val); - - if (callbacks.length) { - nextTick(function() { - var callback; - for (var i = 0, ii = callbacks.length; i < ii; i++) { - callback = callbacks[i]; - value.then(callback[0], callback[1], callback[2]); - } - }); - } - } - }, - - - reject: function(reason) { - deferred.resolve(createInternalRejectedPromise(reason)); - }, + return new Deferred(); + }; + function Promise () { + this.$$pending = []; + } - notify: function(progress) { - if (pending) { - var callbacks = pending; + Promise.prototype = { + then: function(callback, errback, progressback) { + var result = new Deferred(); - if (pending.length) { - nextTick(function() { - var callback; - for (var i = 0, ii = callbacks.length; i < ii; i++) { - callback = callbacks[i]; - callback[2](progress); - } - }); - } + var wrappedCallback = function(value) { + try { + result.resolve((isFunction(callback) ? callback : defaultCallback)(value)); + } catch(e) { + result.reject(e); + exceptionHandler(e); } - }, - - - promise: { - then: function(callback, errback, progressback) { - var result = defer(); + }; + + var wrappedErrback = function(reason) { + try { + result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); + } catch(e) { + result.reject(e); + exceptionHandler(e); + } + }; - var wrappedCallback = function(value) { - try { - result.resolve((isFunction(callback) ? callback : defaultCallback)(value)); - } catch(e) { - result.reject(e); - exceptionHandler(e); - } - }; - - var wrappedErrback = function(reason) { - try { - result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); - } catch(e) { - result.reject(e); - exceptionHandler(e); - } - }; + var wrappedProgressback = function(progress) { + try { + result.notify((isFunction(progressback) ? progressback : defaultCallback)(progress)); + } catch(e) { + exceptionHandler(e); + } + }; - var wrappedProgressback = function(progress) { - try { - result.notify((isFunction(progressback) ? progressback : defaultCallback)(progress)); - } catch(e) { - exceptionHandler(e); - } - }; + if (this.$$pending) { + this.$$pending.push([wrappedCallback, wrappedErrback, wrappedProgressback]); + } else { + this.$$value.then(wrappedCallback, wrappedErrback, wrappedProgressback); + } - if (pending) { - pending.push([wrappedCallback, wrappedErrback, wrappedProgressback]); - } else { - value.then(wrappedCallback, wrappedErrback, wrappedProgressback); - } + return result.promise; + }, + + "catch": function(callback) { + return this.then(null, callback); + }, + "finally": function(callback) { + function makePromise(value, resolved) { + var result = new Deferred(); + if (resolved) { + result.resolve(value); + } else { + result.reject(value); + } + return result.promise; + } - return result.promise; - }, + function handleCallback(value, isResolved) { + var callbackOutput = null; + try { + callbackOutput = (callback ||defaultCallback)(); + } catch(e) { + return makePromise(e, false); + } + if (isPromiseLike(callbackOutput)) { + return callbackOutput.then(function() { + return makePromise(value, isResolved); + }, function(error) { + return makePromise(error, false); + }); + } else { + return makePromise(value, isResolved); + } + } - "catch": function(callback) { - return this.then(null, callback); - }, + return this.then(function(value) { + return handleCallback(value, true); + }, function(error) { + return handleCallback(error, false); + }); + } + }; - "finally": function(callback) { - function makePromise(value, resolved) { - var result = defer(); - if (resolved) { - result.resolve(value); - } else { - result.reject(value); - } - return result.promise; - } + function Deferred () { + this.promise = new Promise(); + //Necessary to support unbound execution :/ + this.resolve = this.resolve.bind(this); + this.reject = this.reject.bind(this); + this.notify = this.notify.bind(this); + } - function handleCallback(value, isResolved) { - var callbackOutput = null; - try { - callbackOutput = (callback ||defaultCallback)(); - } catch(e) { - return makePromise(e, false); + Deferred.prototype = { + resolve: function(val) { + if (this.promise.$$pending) { + var callbacks = this.promise.$$pending; + this.promise.$$pending = undefined; + this.promise.$$value = ref(val); + + if (callbacks.length) { + nextTick(function() { + var callback; + for (var i = 0, ii = callbacks.length; i < ii; i++) { + callback = callbacks[i]; + this.promise.$$value.then(callback[0], callback[1], callback[2]); } - if (isPromiseLike(callbackOutput)) { - return callbackOutput.then(function() { - return makePromise(value, isResolved); - }, function(error) { - return makePromise(error, false); - }); - } else { - return makePromise(value, isResolved); + }.bind(this)); + } + } + }, + reject: function(reason) { + this.resolve(createInternalRejectedPromise(reason)); + }, + notify: function(progress) { + if (this.promise.$$pending) { + var callbacks = this.promise.$$pending; + + if (this.promise.$$pending.length) { + nextTick(function() { + var callback; + for (var i = 0, ii = callbacks.length; i < ii; i++) { + callback = callbacks[i]; + callback[2](progress); } - } - - return this.then(function(value) { - return handleCallback(value, true); - }, function(error) { - return handleCallback(error, false); }); } } - }; - - return deferred; + } }; - var ref = function(value) { if (isPromiseLike(value)) return value; return { then: function(callback) { - var result = defer(); + var result = new Deferred(); nextTick(function() { result.resolve(callback(value)); }); @@ -430,7 +431,7 @@ function qFactory(nextTick, exceptionHandler) { * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. */ var reject = function(reason) { - var result = defer(); + var result = new Deferred(); result.reject(reason); return result.promise; }; @@ -438,7 +439,7 @@ function qFactory(nextTick, exceptionHandler) { var createInternalRejectedPromise = function(reason) { return { then: function(callback, errback) { - var result = defer(); + var result = new Deferred(); nextTick(function() { try { result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); @@ -467,7 +468,7 @@ function qFactory(nextTick, exceptionHandler) { * @returns {Promise} Returns a promise of the passed value or promise */ var when = function(value, callback, errback, progressback) { - var result = defer(), + var result = new Deferred(), done; var wrappedCallback = function(value) { @@ -541,7 +542,7 @@ function qFactory(nextTick, exceptionHandler) { * with the same rejection value. */ function all(promises) { - var deferred = defer(), + var deferred = new Deferred(), counter = 0, results = isArray(promises) ? [] : {}; @@ -575,7 +576,7 @@ function qFactory(nextTick, exceptionHandler) { return new Q(resolver); } - var deferred = defer(); + var deferred = new Deferred(); function resolveFn(value) { deferred.resolve(value); From 97ec839656bf1b33878f37f340a490805554071d Mon Sep 17 00:00:00 2001 From: Jeff Cross Date: Fri, 1 Aug 2014 15:37:07 -0700 Subject: [PATCH 2/3] SQUASH: add faster simpleBind function --- src/ng/q.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ng/q.js b/src/ng/q.js index 2630186662d6..02b5fc886db9 100644 --- a/src/ng/q.js +++ b/src/ng/q.js @@ -337,9 +337,9 @@ function qFactory(nextTick, exceptionHandler) { function Deferred () { this.promise = new Promise(); //Necessary to support unbound execution :/ - this.resolve = this.resolve.bind(this); - this.reject = this.reject.bind(this); - this.notify = this.notify.bind(this); + this.resolve = simpleBind(this, this.resolve); + this.reject = simpleBind(this, this.reject); + this.notify = simpleBind(this, this.notify); } Deferred.prototype = { @@ -350,13 +350,13 @@ function qFactory(nextTick, exceptionHandler) { this.promise.$$value = ref(val); if (callbacks.length) { - nextTick(function() { + nextTick(simpleBind(this, function() { var callback; for (var i = 0, ii = callbacks.length; i < ii; i++) { callback = callbacks[i]; this.promise.$$value.then(callback[0], callback[1], callback[2]); } - }.bind(this)); + })); } } }, From e337920403828abe0c0a8193a751a64a716cd70d Mon Sep 17 00:00:00 2001 From: Jeff Cross Date: Fri, 1 Aug 2014 15:37:34 -0700 Subject: [PATCH 3/3] SQUASH: add move promise settling functions outside of finally --- src/ng/q.js | 66 +++++++++++++++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/src/ng/q.js b/src/ng/q.js index 02b5fc886db9..fd94399deecd 100644 --- a/src/ng/q.js +++ b/src/ng/q.js @@ -297,42 +297,20 @@ function qFactory(nextTick, exceptionHandler) { return this.then(null, callback); }, "finally": function(callback) { - function makePromise(value, resolved) { - var result = new Deferred(); - if (resolved) { - result.resolve(value); - } else { - result.reject(value); - } - return result.promise; - } - - function handleCallback(value, isResolved) { - var callbackOutput = null; - try { - callbackOutput = (callback ||defaultCallback)(); - } catch(e) { - return makePromise(e, false); - } - if (isPromiseLike(callbackOutput)) { - return callbackOutput.then(function() { - return makePromise(value, isResolved); - }, function(error) { - return makePromise(error, false); - }); - } else { - return makePromise(value, isResolved); - } - } - return this.then(function(value) { - return handleCallback(value, true); + return handleCallback(value, true, callback); }, function(error) { - return handleCallback(error, false); + return handleCallback(error, false, callback); }); } }; + //Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native + function simpleBind(context, fn) { + return function() { + fn.apply(context, arguments); + }; + } function Deferred () { this.promise = new Promise(); @@ -436,6 +414,34 @@ function qFactory(nextTick, exceptionHandler) { return result.promise; }; + var makePromise = function makePromise(value, resolved) { + var result = new Deferred(); + if (resolved) { + result.resolve(value); + } else { + result.reject(value); + } + return result.promise; + }; + + var handleCallback = function handleCallback(value, isResolved, callback) { + var callbackOutput = null; + try { + callbackOutput = (callback ||defaultCallback)(); + } catch(e) { + return makePromise(e, false); + } + if (isPromiseLike(callbackOutput)) { + return callbackOutput.then(function() { + return makePromise(value, isResolved); + }, function(error) { + return makePromise(error, false); + }); + } else { + return makePromise(value, isResolved); + } + }; + var createInternalRejectedPromise = function(reason) { return { then: function(callback, errback) {