One of the biggest upcoming additions to Javascript (in my opinion) is support for async/await. If you have worked with a language like C# before, then you will immediately know why async/await is fantastic.
Currently async/await is in stage 3 which means it is almost a completed specification. At the time of writing this post, surprisingly Microsoft’s Edge browser is the only browser which has added in support for async/await.
Support alert:
While async/await will soon be standardised into the TC39 specification by the end of 2016 (hopefully) you still need to use a transpiler like Babel to add in support. At the time of writing this, TypeScript lacks support for async/await, so you need to use Babel with it.
Basic Use With A Promise
Remember async/await is made for use with promises mostly, so it doesn’t replace them, but rather enhances them. Instead of chaining a whole bunch of then
callbacks, we can write code that looks and works synchronously, but as a whole is actually asynchronous (like a promise).
export class MyViewModel {
getData() {
return new Promise((resolve, reject) => {
// http is either the aurelia-fetch-client or aurelia-http-client
this.http.get('/mydata').then(data => {
resolve(data);
});
});
}
async decorateData() {
let data = await this.getData();
// Before the following return is run, the await will halt
// execution before the function continues
return {version: 1, data: data};
}
}
Async/await with Aurelia lifecycle methods
Aurelia allows you to use promises inside of router lifecycle methods like canActivate
and activate
this means we can also specify them as async functions and use await to halt activation until data is loaded.
export class MyViewModel {
myData = [];
async activate() {
// http is either the aurelia-fetch-client or aurelia-http-client
let response = await this.http.get('/mydata');
this.myData = response.content;
}
}
Error Handling Async/Await
You might notice in the above examples we have no concept of error handling. When working with just promises we generally use .then
to capture a successful promise resolution or we use .catch
to catch any rejected promises.
The way errors are captured in async functions is similar to how you might catch errors with other bits of code you are running in your application (not promise specific).
export class MyViewModel {
getData() {
return new Promise((resolve, reject) => {
// http is either the aurelia-fetch-client or aurelia-http-client
this.http.get('/mydata').then(data => {
resolve(data);
});
});
}
async decorateData() {
try {
let data = await this.getData();
return {version: 1, data: data};
} catch(error) {
console.error(error);
return null;
}
}
}
Multiple Awaits
It isn’t uncommon to potentially have a chain of promises all feeding off one another. In an application I am working on I have one such scenario where pieces are loaded based on previous data, this requires chaining a lot of thennables on my promises and it looks horrendous.
I don’t know what you prefer, but I think it is pretty obvious which one wins out of the two identical examples (one using async/await and one using just promises).
Using Async/Await
The following example is a little contrived, it could actually be simpler, but I wanted to make sure it was obvious what was going on.
export class MyViewModel {
async doStuff() {
try {
let data1 = await this.http.get('/myData/1');
let data2 = await this.http.get('/myData/2');
let data3 = await this.http.get('/myData/3');
this.data1 = data1.content || '';
this.data2 = data1.content || '';
this.data3 = data1.content || '';
} catch(error) {
console.error(error);
}
}
}
Using Promises
export class MyViewModel {
doStuff() {
return Promise.all([
this.http.get('/myData/1'),
this.http.get('/myData/2'),
this.http.get('/myData/3')
]).then(responses => {
this.data1 = responses[0].content;
this.data2 = responses[1].content;
this.data3 = responses[2].content;
}).catch(error => {
console.error(error);
});
}
}
Conclusion
With exception of being able to make Aurelia lifecycle methods async, there isn’t anything you need to specifically do to use async/await in your Aurelia application (except needing a transpiler).
Typescript *does* support it.
In addition, why would you want to load resources synchronously?
Might be better to write
const [data1, data2, data3] = await Promise.all([
this.http.get(‘/myData/1’),
this.http.get(‘/myData/2’),
this.http.get(‘/myData/3’),
]);
Much more performant and all the requests are fetched at the same time.
if you have to fill data from two different service, thats a good reason for sync calls