Let's say you're working on an old AngularJS 1.x codebase and you want to use async / await. These keywords don't play nice with AngularJS. Once these promises are fulfilled, you have to wrap them in $q
(or run $apply()
, etc.) in order to loop them into angular's digest cycle. This sucks!
We can use babel to transpile async / await and do the $q-wrapping for us.
The only caveat is that we can only call await
after angular bootstrap. You can still use Promise.then
.
Here's how to do this:
- Create a file called
ag-await.js
:
/* eslint-disable no-undef */
// Adapted from: https://labs.magnet.me/nerds/2015/11/16/async-await-in-angularjs.html
// Babel will convert "await" to a generator, and then wrap the generator with this function
// This only works if we use await after the app has been bootstrapped
//
// This is intentionally ES5, because it's not clear if this will go through babel since it's
// a babel plugin
export function $await(generator) {
function getInjector() {
try {
var angular = window.angular;
if (!angular) throw new Error('Angular is not available on window!');
var $injector = angular.element(document).injector();
if (!$injector) throw new Error('Could not get angular injector for document.');
return $injector;
} catch (error) {
console.error(
[
'This error is probably because you have an async / await call before angular bootstrap.',
'Use the stack trace to find out where the issue is, and re-organize your code such that the',
'await is done after angular bootstrap. Alternatively, exclude the file from the babel config.',
].join('\n'),
);
throw error;
}
}
return function () {
var args = arguments;
var self = this;
var $injector = getInjector();
var $q = $injector.get('$q');
var $rootScope = $injector.get('$rootScope');
var promise = (function () {
var deferred = $q.defer();
var iter;
try {
iter = generator.apply(self, args);
} catch (e) {
deferred.reject(e);
return;
}
function next(val, isError = false) {
var state;
try {
state = isError ? iter.throw(val) : iter.next(val);
} catch (e) {
deferred.reject(e);
return;
}
if (state.done) {
deferred.resolve(state.value);
} else {
$q.when(state.value).then(next, function (err) {
next(err, true);
});
}
}
next();
return deferred.promise;
})();
return promise['finally'](function () {
try {
$rootScope.$evalAsync();
// eslint-disable-next-line no-empty
} catch (error) {}
});
};
}
Then in your babel config:
{
plugins: [
[
'@babel/plugin-transform-async-to-generator',
{
module: require.resolve('./ag-await.js'),
method: '$await',
},
],
'@babel/plugin-transform-regenerator'
],
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: 3,
targets: '> 0.25%, not dead',
bugfixes: true,
exclude: [
'@babel/plugin-transform-regenerator',
'@babel/plugin-proposal-async-generator-functions',
'@babel/plugin-transform-async-to-generator',
],
},
]
],
}
You can now do this:
app.controller("Foo", ($scope) => {
const response = await fetch('https://example.com/data.json');
$scope.data = await response.json();
})
Top comments (0)