Promise
Promise is an attempt to address the problems caused by callback. It bundles the future results of an asynchronous call into an object. AngularJS Promise is implemented by pairs of deferred and promise objects that are made of a promise chain, and the digest loop of AngularJS Scope, which schedules the asynchronous jobs(callbacks) to get invoked in the next digest loop.
Deferred Object
// Whenever you create a Deferred, you also create a Promise
function Deferred() {
this.promise = new Promise();
}
Deferred.prototype.resolve = function(value) {
if (this.promise.$$state.status) { // only resolve once
return;
}
if (value && _.isFunction(value.then)) {
/*
* Case: a Promise returns another Promise
* Solution: connect the Promise returned by the callback to the next * callback in the chain. The Deferred's resolution is dependent on * the resolution of the other Promise, so we attach the Deferred's
* resolve and reject method to the returned Promise using then(..) * and bind(..). Then resolve method will get invoked again when the * returned Promise resolves.
*/
value.then(
_.bind(this.resolve, this),
_.bind(this.reject, this));
} else {
this.promise.$$state.value = value; //future results
this.promise.$$state.status = 1;
scheduleProcessQueue(this.promise.$$state);
}
};function defer() {
return new Deferred();
}
Promise Object
function Promise() {
this.$$state = {};
}/*
* then(..): Register this promise with callbacks
* 1. define two callbacks on this promise
* 2. create the next pair of deferred and promise in the chain
*/
Promise.prototype.then = function(onFulfilled, onRejected) {
/* create a new Deferred that represents the computation in the onFulfilled callbacks. */
var result = new Deferred();
this.$$state.pending = this.$$state.pending || [];
/* pass this Deferred to where the onFulfilled callback actually gets invoked, so that the results can be delivered. */
this.$$state.pending.push([result, onFulfilled, onRejected]);
/*
* Case: If Promise callbacks are registered after the corresponding * deferred was already resolved and a digest was run.
* Solution: check the status flag at callback registration time.
*/
if (this.$$state.status > 0) {
scheduleProcessQueue(this.$$state);
}
return result.promise;
};
Defer Callbacks
- $evalAsync function of the root scope: this function can be used to attach callbacks that run during the next digest, and that it also schedules a digest to happen with setTimeout if one hasn’t been scheduled already.
function $QProvider() {
this.$get = ['$rootScope', function($rootScope) {
return qFactory(function(callback) {
$rootScope.$evalAsync(callback);
});
}];
}
- setTimeout(..)
“function $$QProvider() {
this.$get = function() {
return qFactory(function(callback) {
setTimeout(callback, 0);
});
};
}
Propagation
function qFactory(callLater) {function processQueue(state) {
var pending = state.pending;
// At each digest, pending callbacks are cleared out.
state.pending = undefined;
_.forEach(pending, function(handlers) {
var deferred = handlers[0];
var fn = handlers[state.status];
try {
if (_.isFunction(fn)) {
/* call the callbacks of the promise with the future results, and then pass down the return value of this callback in the chain */
deferred.resolve(fn(state.value));
} else if (state.status === 1) {
deferred.resolve(state.value);
} else {
deferred.reject(state.value);
}
} catch (e) {
deferred.reject(e);
}
});
}function scheduleProcessQueue(state) {
callLater(function() {
processQueue(state);
});
}
$q.all()
The $q.all() method takes a collection of Promises as its input. It returns a single Promise that resolves to an array of results. It is a useful tool for waiting on a number of simultaneous asynchronous tasks to finish.
/*
* Counter: an integer that increments each time a Promise *callback is added, and decrements each time a callback is called.
*/
function all(promises) {
var results = _.isArray(promises) ? [] : {};
var counter = 0;
var d = defer();
_.forEach(promises, function(promise, index) {
counter++;
promise.then(function(value) {
results[index] = value;
counter--;
if (!counter) {
d.resolve(results);
}
}, function(rejection) {
d.reject(rejection);
});
});
return d.promise;
}
ES2015-Style Promise
Like an ES2015 Promise constructor, this Q function expects to get a function as an argument. And it will get invoked immediately with callbacks.
var $Q = function Q(resolver) {
if (!_.isFunction(resolver)) {
throw 'Expected function, got ' + resolver;
}
var d = defer();
resolver(
_.bind(d.resolve, d),
_.bind(d.reject, d)
);
return d.promise;
};