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.

Closes #8300
This commit is contained in:
Jeff Cross
2014-07-30 16:08:20 -07:00
parent 48b34ddb79
commit 23bc92b17d

View File

@@ -247,143 +247,122 @@ function qFactory(nextTick, exceptionHandler) {
* @returns {Deferred} Returns a new instance of deferred. * @returns {Deferred} Returns a new instance of deferred.
*/ */
var defer = function() { var defer = function() {
var pending = [], return new Deferred();
value, deferred; };
deferred = { function Promise () {
this.$$pending = [];
}
resolve: function(val) { Promise.prototype = {
if (pending) { then: function(callback, errback, progressback) {
var callbacks = pending; var result = new Deferred();
pending = undefined;
value = ref(val);
if (callbacks.length) { var wrappedCallback = function(value) {
nextTick(function() { try {
var callback; result.resolve((isFunction(callback) ? callback : defaultCallback)(value));
for (var i = 0, ii = callbacks.length; i < ii; i++) { } catch(e) {
callback = callbacks[i]; result.reject(e);
value.then(callback[0], callback[1], callback[2]); exceptionHandler(e);
}
});
}
} }
}, };
var wrappedErrback = function(reason) {
reject: function(reason) { try {
deferred.resolve(createInternalRejectedPromise(reason)); result.resolve((isFunction(errback) ? errback : defaultErrback)(reason));
}, } catch(e) {
result.reject(e);
exceptionHandler(e);
notify: function(progress) {
if (pending) {
var callbacks = pending;
if (pending.length) {
nextTick(function() {
var callback;
for (var i = 0, ii = callbacks.length; i < ii; i++) {
callback = callbacks[i];
callback[2](progress);
}
});
}
} }
}, };
var wrappedProgressback = function(progress) {
try {
result.notify((isFunction(progressback) ? progressback : defaultCallback)(progress));
} catch(e) {
exceptionHandler(e);
}
};
promise: { if (this.$$pending) {
then: function(callback, errback, progressback) { this.$$pending.push([wrappedCallback, wrappedErrback, wrappedProgressback]);
var result = defer(); } else {
this.$$value.then(wrappedCallback, wrappedErrback, wrappedProgressback);
}
var wrappedCallback = function(value) { return result.promise;
try { },
result.resolve((isFunction(callback) ? callback : defaultCallback)(value));
} catch(e) { "catch": function(callback) {
result.reject(e); return this.then(null, callback);
exceptionHandler(e); },
"finally": function(callback) {
return this.then(function(value) {
return handleCallback(value, true, callback);
}, function(error) {
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(value) {
fn.call(context, value);
};
}
function Deferred () {
this.promise = new Promise();
//Necessary to support unbound execution :/
this.resolve = simpleBind(this, this.resolve);
this.reject = simpleBind(this, this.reject);
this.notify = simpleBind(this, this.notify);
}
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(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]);
} }
}; }));
}
}
},
reject: function(reason) {
this.resolve(createInternalRejectedPromise(reason));
},
notify: function(progress) {
if (this.promise.$$pending) {
var callbacks = this.promise.$$pending;
var wrappedErrback = function(reason) { if (this.promise.$$pending.length) {
try { nextTick(function() {
result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); var callback;
} catch(e) { for (var i = 0, ii = callbacks.length; i < ii; i++) {
result.reject(e); callback = callbacks[i];
exceptionHandler(e); callback[2](progress);
} }
};
var wrappedProgressback = function(progress) {
try {
result.notify((isFunction(progressback) ? progressback : defaultCallback)(progress));
} catch(e) {
exceptionHandler(e);
}
};
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 = defer();
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);
}, function(error) {
return handleCallback(error, false);
}); });
} }
} }
}; }
return deferred;
}; };
var ref = function(value) { var ref = function(value) {
if (isPromiseLike(value)) return value; if (isPromiseLike(value)) return value;
return { return {
then: function(callback) { then: function(callback) {
var result = defer(); var result = new Deferred();
nextTick(function() { nextTick(function() {
result.resolve(callback(value)); result.resolve(callback(value));
}); });
@@ -430,15 +409,43 @@ function qFactory(nextTick, exceptionHandler) {
* @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
*/ */
var reject = function(reason) { var reject = function(reason) {
var result = defer(); var result = new Deferred();
result.reject(reason); result.reject(reason);
return result.promise; 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) { var createInternalRejectedPromise = function(reason) {
return { return {
then: function(callback, errback) { then: function(callback, errback) {
var result = defer(); var result = new Deferred();
nextTick(function() { nextTick(function() {
try { try {
result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); result.resolve((isFunction(errback) ? errback : defaultErrback)(reason));
@@ -467,7 +474,7 @@ function qFactory(nextTick, exceptionHandler) {
* @returns {Promise} Returns a promise of the passed value or promise * @returns {Promise} Returns a promise of the passed value or promise
*/ */
var when = function(value, callback, errback, progressback) { var when = function(value, callback, errback, progressback) {
var result = defer(), var result = new Deferred(),
done; done;
var wrappedCallback = function(value) { var wrappedCallback = function(value) {
@@ -541,7 +548,7 @@ function qFactory(nextTick, exceptionHandler) {
* with the same rejection value. * with the same rejection value.
*/ */
function all(promises) { function all(promises) {
var deferred = defer(), var deferred = new Deferred(),
counter = 0, counter = 0,
results = isArray(promises) ? [] : {}; results = isArray(promises) ? [] : {};
@@ -575,7 +582,7 @@ function qFactory(nextTick, exceptionHandler) {
return new Q(resolver); return new Q(resolver);
} }
var deferred = defer(); var deferred = new Deferred();
function resolveFn(value) { function resolveFn(value) {
deferred.resolve(value); deferred.resolve(value);