I\'m replacing $http
with Fetch API and got replaced $q
with Promise API. Because of that, Angular didn\'t run digest cycles anymore, thus UI didn\
Wrapping $q.when
will work but in my team's experience it will be very finicky and prone to error. As one example, returning $q.when
from inside the body of a Promise.then
function will still chain as a regular Promise
and you won't get a $digest on callbacks.
It also requires all authors to understand the difference between two very similar looking constructs (Promise/$q) and care about their concrete types for every level of an asynchronous call. If you are using modern conveniences like async
/await
(which abstracts the Promise types further), you're gonna be in even more trouble. Suddenly none of your code can be framework agnostic.
Our team decided it was worth committing a big monkey patch to ensure all the promises (and the async
/await
keywords) "just worked" without needing additional thinking.
Ugly? Yes. But we felt it was an okay tradeoff.
First we install the patch against Promise
in a angular.run
block:
angular.module(...).run(normalizePromiseSideEffects);
normalizePromiseSideEffects.$inject = ['$rootScope'];
function normalizePromiseSideEffects($rootScope) {
attachScopeApplicationToPromiseMethod('then');
attachScopeApplicationToPromiseMethod('catch');
attachScopeApplicationToPromiseMethod('finally');
function attachScopeApplicationToPromiseMethod(methodName) {
const NativePromiseAPI = window.Promise;
const nativeImplementation = NativePromiseAPI.prototype[methodName];
NativePromiseAPI.prototype[methodName] = function(...promiseArgs) {
const newPromiseArgs = promiseArgs.map(wrapFunctionInScopeApplication);
return nativeImplementation.bind(this)(...newPromiseArgs);
};
}
function wrapFunctionInScopeApplication(fn) {
if (!isFunction(fn) || fn.isScopeApplicationWrapped) {
return fn;
}
const wrappedFn = (...args) => {
const result = fn(...args);
// this API is used since it's $q was using in AngularJS src
$rootScope.$evalAsync();
return result;
};
wrappedFn.isScopeApplicationWrapped = true;
return wrappedFn;
}
}
If you want to support the use of async
/await
, you'll also need to configure Babel to always implement the syntax as Promises. We used babel-plugin-transform-async-to-promises.