Using Typescrip 2.1 async and await in an Angular 1 application (Yes! Yes! Yes!!)

Two blogposts in one week? Yes! I was so excited about this subject I could not stop myself from sharing this with the rest of the world. As a C# developer, I actually love the async and await feature that that language offers. I think it makes asynchronous code a lot easier to write and to read (although you do need know what is happening under the hood).

Asynchronous code is not unique to C#. It also happens a lot when you are writing JavaScript /TypeScript. When you are using Angular 1, you will probably know that Angular uses promises to represent a piece of work that finishes in the future. Angular promises and the Angular $q service that you can use to work with promises are based on the CommonJS specification for promises. These promises are working fine, but they can cause your code to become less readable because of all the callbacks that are happening when doing promise programming, especially when you have nested asynchronous calls together with failure handlers for the promises.

See the screen shot below of some asynchronous code from an application I am currently working on.

Capture

On itself this is not one of the worst parts, but you can see some nesting going on and some then-calls with anonymous functions. I would love to rewrite this to the new async/await features of Typescript 2.1.  Also take note of the way I create a promise that resolves immediately, using $q.when().

Async/await was available before Typescript 2.1 but it was only available when targeting ECMA 6 or ECMA 2015. Typescript 2.1 adds support for down level async functions, meaning that we can also target ES 5 or 3.

When using async/await on a ES 5 or 3 environment we have to make sure we have an ES 6 compatible  promise implementation. The generated JavaScript, when using async/wait is based on the ES 6 Promise specification. So we need to polyfill the Window.Promise constructor function. But we can’t just use any polyfill because in an Angular application, the default $q promises also call $apply when a promise resolves to refresh the UI. If we would grab just a polyfill from the internet we have to add the $apply calls ourselves. That would be a lot of work.

The trick to solve this is really easy! Luckily angular comes with an ES 6 polyfill out of the box! If you look at the documentation for $q, you will see that it also has an ES 6 compatible interface. Instead of using the defer api when creating promises, you can also use the ES 6 way of creating promises. You can actually use the new keyword on $q and pass it a function to create a new promise. Or instead of using $q.when to create a promise that resolves immediately you can also use $q.resolve, just like in the ES 6 specification.

So now that we have established that we can use $q to polyfill the window.Promise, here is the way to do it.

In the run method of my  main module I injected the following piece of code:

Capture

The entire run method looks like this (just to give some context)

Capture

The promise polyfilling happens on the last line of the run method.

So that takes care of the runtime stuff. Runtime, the generated async code has everything it needs to run without errors. But compile time we are still not there. Typescript does  not know that we have a valid Promise implementation when targeting an ECMA version lower than 6. The Typescript compiler has a new option for this. The –lib option. You can pass this option an array of strings, to tell the compiler which standard declaration files the compiler should be using when compiling. I am compiling my application by using gulp and gulp-typescript, so my gulp file has been modified to look like this:

Capture

You can see the list of strings I pass to the lib option. The ES2015.Promise string does the trick for the promises. Normally this list is populated for you when you specify a target. But when you pass the lib option yourself, this does not happen so I also have to include the other declaration files that I want to be there (for accessing the DOM for example).

Now we have everything in place to rewrite the code that we saw earlier is to async/await. Here it is!

Capture

This looks a bit more readable! There are no more anonymous functions for hooking then handlers to promises. I can also use the normal try catch constructs if I want instead of the failure handlers. Also notice The Promise.resolve() call I now use to create a promise that completes. This more ES 6 style.  I also modified the services to return Promise instead of ng.IPromise to make sure the compiler does not complain, runtime the types are interchangeable.

One last tip. If you are using gulp-typescript to compile, make sure you use version 3! As version 2.x grabs it’s own version of typescript (you can override this), but version 3 will grab the typescript version you have in your package.json.

Happy asynchronous coding!

Chris

Tweet about this on TwitterShare on LinkedIn

Reacties

  • Hi, thanks for the post!

    In my case typescript targets ES6, but Babel then transpiles code to ES5. Is there a way to make TypeScript compiler substitute es6 Promise definitions with $q IPsomise definitions?

Het e-mailadres wordt niet gepubliceerd.

*