Skip to content
Permalink
Browse files

refactor($q): separate Promise from Deferred

Closes #15064

BREAKING CHANGE:

Previously, the `Deferred` object returned by `$q.defer()` delegated the
`resolve()`, `reject()` and `notify()` methods to `Deferred.prototype`. Thus, it
was possible to modify `Deferred.prototype` and have the changes reflect to all
`Deferred` objects.

This commit removes that delegation, so modifying the above three methods on
`Deferred.prototype` will no longer have an effect on `Deferred` objects.
  • Loading branch information
jbedard authored and gkalpak committed Jun 8, 2016
1 parent cdf3d5e commit 34434cf528184290ce25fd11066ff8a3fa760519
Showing with 97 additions and 103 deletions.
  1. +97 −103 src/ng/q.js
@@ -299,14 +299,18 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) {
* @returns {Deferred} Returns a new instance of deferred.
*/
function defer() {
var d = new Deferred();
//Necessary to support unbound execution :/
d.resolve = simpleBind(d, d.resolve);
d.reject = simpleBind(d, d.reject);
d.notify = simpleBind(d, d.notify);
return d;
return new Deferred();
}

function Deferred() {
var promise = this.promise = new Promise();
//Non prototype methods necessary to support unbound execution :/
this.resolve = function(val) { resolvePromise(promise, val); };
this.reject = function(reason) { rejectPromise(promise, reason); };
this.notify = function(progress) { notifyPromise(promise, progress); };
}


function Promise() {
this.$$state = { status: 0 };
}
@@ -316,13 +320,13 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) {
if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) {
return this;
}
var result = new Deferred();
var result = new Promise();

this.$$state.pending = this.$$state.pending || [];
this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]);
if (this.$$state.status > 0) scheduleProcessQueue(this.$$state);

return result.promise;
return result;
},

'catch': function(callback) {
@@ -338,34 +342,27 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) {
}
});

//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 processQueue(state) {
var fn, deferred, pending;
var fn, promise, pending;

pending = state.pending;
state.processScheduled = false;
state.pending = undefined;
try {
for (var i = 0, ii = pending.length; i < ii; ++i) {
state.pur = true;
deferred = pending[i][0];
promise = pending[i][0];
fn = pending[i][state.status];
try {
if (isFunction(fn)) {
deferred.resolve(fn(state.value));
resolvePromise(promise, fn(state.value));
} else if (state.status === 1) {
deferred.resolve(state.value);
resolvePromise(promise, state.value);
} else {
deferred.reject(state.value);
rejectPromise(promise, state.value);
}
} catch (e) {
deferred.reject(e);
rejectPromise(promise, e);
}
}
} finally {
@@ -401,83 +398,80 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) {
nextTick(function() { processQueue(state); });
}

function Deferred() {
this.promise = new Promise();
function resolvePromise(promise, val) {
if (promise.$$state.status) return;
if (val === promise) {
$$reject(promise, $qMinErr(
'qcycle',
'Expected promise to be resolved with value other than itself \'{0}\'',
val));
} else {
$$resolve(promise, val);
}

}

extend(Deferred.prototype, {
resolve: function(val) {
if (this.promise.$$state.status) return;
if (val === this.promise) {
this.$$reject($qMinErr(
'qcycle',
'Expected promise to be resolved with value other than itself \'{0}\'',
val));
function $$resolve(promise, val) {
var then;
var done = false;
try {
if (isObject(val) || isFunction(val)) then = val.then;
if (isFunction(then)) {
promise.$$state.status = -1;
then.call(val, doResolve, doReject, doNotify);
} else {
this.$$resolve(val);
}
},

$$resolve: function(val) {
var then;
var that = this;
var done = false;
try {
if (isObject(val) || isFunction(val)) then = val.then;
if (isFunction(then)) {
this.promise.$$state.status = -1;
then.call(val, resolvePromise, rejectPromise, simpleBind(this, this.notify));
} else {
this.promise.$$state.value = val;
this.promise.$$state.status = 1;
scheduleProcessQueue(this.promise.$$state);
}
} catch (e) {
rejectPromise(e);
promise.$$state.value = val;
promise.$$state.status = 1;
scheduleProcessQueue(promise.$$state);
}
} catch (e) {
doReject(e);
}

function resolvePromise(val) {
if (done) return;
done = true;
that.$$resolve(val);
}
function rejectPromise(val) {
if (done) return;
done = true;
that.$$reject(val);
}
},
function doResolve(val) {
if (done) return;
done = true;
$$resolve(promise, val);
}
function doReject(val) {
if (done) return;
done = true;
$$reject(promise, val);
}
function doNotify(progress) {
notifyPromise(promise, progress);
}
}

reject: function(reason) {
if (this.promise.$$state.status) return;
this.$$reject(reason);
},
function rejectPromise(promise, reason) {
if (promise.$$state.status) return;
$$reject(promise, reason);
}

$$reject: function(reason) {
this.promise.$$state.value = reason;
this.promise.$$state.status = 2;
scheduleProcessQueue(this.promise.$$state);
},
function $$reject(promise, reason) {
promise.$$state.value = reason;
promise.$$state.status = 2;
scheduleProcessQueue(promise.$$state);
}

notify: function(progress) {
var callbacks = this.promise.$$state.pending;

if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) {
nextTick(function() {
var callback, result;
for (var i = 0, ii = callbacks.length; i < ii; i++) {
result = callbacks[i][0];
callback = callbacks[i][3];
try {
result.notify(isFunction(callback) ? callback(progress) : progress);
} catch (e) {
exceptionHandler(e);
}
function notifyPromise(promise, progress) {
var callbacks = promise.$$state.pending;

if ((promise.$$state.status <= 0) && callbacks && callbacks.length) {
nextTick(function() {
var callback, result;
for (var i = 0, ii = callbacks.length; i < ii; i++) {
result = callbacks[i][0];
callback = callbacks[i][3];
try {
notifyPromise(result, isFunction(callback) ? callback(progress) : progress);
} catch (e) {
exceptionHandler(e);
}
});
}
}
});
}
});
}

/**
* @ngdoc method
@@ -516,9 +510,9 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) {
* @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
*/
function reject(reason) {
var result = new Deferred();
result.reject(reason);
return result.promise;
var result = new Promise();
rejectPromise(result, reason);
return result;
}

function handleCallback(value, resolver, callback) {
@@ -556,9 +550,9 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) {


function when(value, callback, errback, progressBack) {
var result = new Deferred();
result.resolve(value);
return result.promise.then(callback, errback, progressBack);
var result = new Promise();
resolvePromise(result, value);
return result.then(callback, errback, progressBack);
}

/**
@@ -594,25 +588,25 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) {
*/

function all(promises) {
var deferred = new Deferred(),
var result = new Promise(),
counter = 0,
results = isArray(promises) ? [] : {};

forEach(promises, function(promise, key) {
counter++;
when(promise).then(function(value) {
results[key] = value;
if (!(--counter)) deferred.resolve(results);
if (!(--counter)) resolvePromise(result, results);
}, function(reason) {
deferred.reject(reason);
rejectPromise(result, reason);
});
});

if (counter === 0) {
deferred.resolve(results);
resolvePromise(result, results);
}

return deferred.promise;
return result;
}

/**
@@ -644,19 +638,19 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) {
throw $qMinErr('norslvr', 'Expected resolverFn, got \'{0}\'', resolver);
}

var deferred = new Deferred();
var promise = new Promise();

function resolveFn(value) {
deferred.resolve(value);
resolvePromise(promise, value);
}

function rejectFn(reason) {
deferred.reject(reason);
rejectPromise(promise, reason);
}

resolver(resolveFn, rejectFn);

return deferred.promise;
return promise;
}

// Let's make the instanceof operator work for promises, so that

0 comments on commit 34434cf

Please sign in to comment.
You can’t perform that action at this time.