DEV Community

loading...

Angular 1.x - Transparent Async / Await with Babel 7

surjikal profile image Nick Porter ・2 min read

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 use babel to convert async / await to generators, then wrap them in script that resolves the genrate with $q. See https://labs.magnet.me/nerds/2015/11/16/async-await-in-angularjs.html for more details.

The only caveat is that we can only call await after angular bootstrap. You can still use Promise.then.

This is a quick and dirty proof of concept. It works great, and it should be relatively easy to improve it.

  1. 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

var $injector;

function getInjector() {
    if ($injector) return $injector;
    try {
        if (!window.angular) throw new Error('Angular is not available on window!');
        $injector = window.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.',
            ].join('\n'),
        );
        throw error;
    }
}

module.exports.$await = generator => {
    return function () {
        var args = arguments;
        var self = this;
        $injector = getInjector();
        var $q = $injector.get('$q');
        var $rootScope = $injector.get('$rootScope');
        return $q.when(
            $q(function (resolve, reject) {
                var it;
                try {
                    it = generator.apply(self, args);
                } catch (e) {
                    reject(e);
                    return;
                }
                function next(val, isError = false) {
                    var state;
                    try {
                        state = isError ? it.throw(val) : it.next(val);
                    } catch (e) {
                        $rootScope.$evalAsync();
                        reject(e);
                        return;
                    }
                    if (state.done) {
                        $rootScope.$evalAsync();
                        resolve(state.value);
                    } else {
                        $q.when(state.value).then(next, function (err) {
                            next(err, true);
                        });
                    }
                }
                next();
            }),
        );
    };
};

Enter fullscreen mode Exit fullscreen mode

Then in your babel config:

{
        plugins: [
            [
                '@babel/plugin-transform-async-to-generator',
                {
                    module: require.resolve('./ag-await.js'),
                    method: '$await',
                },
            ],
        ],
        presets: [
            [
                '@babel/preset-env',
                {
                    ...
                    // these are handled by the plugin
                    exclude: [
                        '@babel/plugin-transform-regenerator',
                        '@babel/plugin-proposal-async-generator-functions',
                        '@babel/plugin-transform-async-to-generator',
                    ],
                },
            ],
        ],
    }
Enter fullscreen mode Exit fullscreen mode

Discussion (0)

pic
Editor guide