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.
This commit is contained in:
committed by
Georgios Kalpakas
parent
cdf3d5e054
commit
34434cf528
+102
-108
@@ -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,15 +342,8 @@ 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;
|
||||
@@ -354,18 +351,18 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) {
|
||||
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);
|
||||
}
|
||||
|
||||
function resolvePromise(val) {
|
||||
if (done) return;
|
||||
done = true;
|
||||
that.$$resolve(val);
|
||||
}
|
||||
function rejectPromise(val) {
|
||||
if (done) return;
|
||||
done = true;
|
||||
that.$$reject(val);
|
||||
}
|
||||
},
|
||||
|
||||
reject: function(reason) {
|
||||
if (this.promise.$$state.status) return;
|
||||
this.$$reject(reason);
|
||||
},
|
||||
|
||||
$$reject: function(reason) {
|
||||
this.promise.$$state.value = reason;
|
||||
this.promise.$$state.status = 2;
|
||||
scheduleProcessQueue(this.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);
|
||||
}
|
||||
}
|
||||
});
|
||||
promise.$$state.value = val;
|
||||
promise.$$state.status = 1;
|
||||
scheduleProcessQueue(promise.$$state);
|
||||
}
|
||||
} catch (e) {
|
||||
doReject(e);
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
function rejectPromise(promise, reason) {
|
||||
if (promise.$$state.status) return;
|
||||
$$reject(promise, reason);
|
||||
}
|
||||
|
||||
function $$reject(promise, reason) {
|
||||
promise.$$state.value = reason;
|
||||
promise.$$state.status = 2;
|
||||
scheduleProcessQueue(promise.$$state);
|
||||
}
|
||||
|
||||
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,7 +588,7 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) {
|
||||
*/
|
||||
|
||||
function all(promises) {
|
||||
var deferred = new Deferred(),
|
||||
var result = new Promise(),
|
||||
counter = 0,
|
||||
results = isArray(promises) ? [] : {};
|
||||
|
||||
@@ -602,17 +596,17 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user