Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

refactor($q): separate Deferred out of Promise implementation #15064

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 97 additions & 103 deletions src/ng/q.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a small breaking change. Before, it was possible to intercept all calls to Deferred.prototype.resolve by changing the prototype of Deferred (that you can do by getting the prototype from an instance).

I think this is a small edge case, but should be clarified and the commit should make this clear.

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 };
}
Expand All @@ -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) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was here before but why are catch and finally defined with strings and then is not?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are javascript keywords

Copy link

@aciccarello aciccarello Sep 2, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I should have realized that with catch.

Expand All @@ -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 {
Expand Down Expand Up @@ -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 && 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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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
Expand Down