Flow control in JS is hard, first of all, I’m gonna go with a quick review through promises because this is the foundation of Async + Await.
Promises
Promises in JS are like something is going to happen at some point in the future, so this could be
- access to a user’s webcam
- ajax call
- resizing an image
- or something else
All of these take time, and with promises, we kick in the process and move along, we come back when we need to deal with the data.
Let’s say we wanted to do a few things:
- Learn Javascript
- Write Javascript
- Learn Vue
- Write Vue
Would it make sense to first learn javascript before you start learning vue, would it make sense to wait until you learn javascript and start writing vue before you even start learning vue? No that doesn’t make sense.
We want to start one thing and come back to it once it’s done, and deal with the result correspondingly!
Promises allowed us to start writing code like this:
// start both things, one after another
const jsLearnPromise = learnJs();
const vueLearnPromise = learnVue();
// then once each are done, deal with them
jsLearnPromise.then(js => {
writeJs();
})
vueLearnPromise.then(([vuex, vueRouter])=>{
writeVue(vuex, vueRouter);
})
// you can also wait until both are done
Promise
.all([jsLearnPromise, vueLearnPromise])
.then(([js, vue]) => {
codeWith(js, vue);
});
Most of the new browser APIs are built on promises, so we have got fetch()
, where you can fetch your data then convert it into JSON and then finally deal with the data.
// fetch
fetch('http://learnprograming.org')
.then(data => data.json())
.then(vueFramework => learn(vue));
We can use a library called AXIOS, which have really good built-in defaults where we don’t have to have that second .then()
chain like on the following example
//AXIOS fetch
axios.get('http://learnprograming.org')
.then(vueFramework => learn(vue));
There are many many more browser APIs payment requests dealing with credit card data in the browser, getting user media, getting access to the webcam, web animations… All of these things are being built on standard promises and it’s easy to make your own with promises as well, here we have a function called sleep:
function sleep(amount) {
return new Promise((resolve, reject)=> {
if(amount <=300) {
return reject('That is too fast, cool it down')
}
setTimeout(() => resolve(`Slept for ${amount}`), amount)
});
}
where it takes the amount and the way of promise works is immediately you return a promise and then what you do inside of that promise is:
- you either resolve it when things went well
or - you reject it when things didn’t go well
In this case, after 500 milliseconds we’re going to resolve it with some data or if it’s less than 300 milliseconds I’m going to reject it because that’s too fast. And what that will allow us to do is a little bit something where we can write our code and then we can chain .then().then().then()
on it and by returning a new promise from each one.
sleep(500)
.then((result)=> {
console.log(result)
return sleep(1000);
})
.then((result)=> {
console.log(result)
return sleep(750);
})
.then((result)=> {
console.log(result)
console.log('done');
})
So promises are really really great but what’s the deal with the .then()
? It is still a kind of callback-y and any code that needs to come after the promise still needs to be in the final .then()
callback, it can’t just be top-level in your current function, this is where ASYNC + AWAIT comes in.
Async/Await
Async await is still based on promises but it’s really a nice syntax to work with it.
JavaScript is almost entirely asynchronous/non-blocking.
Async await gives us synchronous looking code without the downside that is actually writing synchronous code! So how does it work? The first thing we need to do is mark our function as async so you still keep your regular promise functions, nothing changes with your functions that return a promise, what we now do is create an async function by writing the word “async” in front of it
async function sleep(){
//.....
}
then, when you are inside an async function, you simply just await things inside it, so you can either just await the sleep function and that will just wait until the promise resolves or if you care about what’s coming back from that promise, maybe it’s some data from an API then we can store that in a variable.
async function sleep() {
//just wait
await sleep(1000);
//or capture the returned value
const response = await sleep(750);
console.log(response);
}
Let’s take a look at another example, in this case, I’m capturing the returned value, that’s another way you can write it.
const getDetails = async function() {
const dave = await axios.get('https://api.github.com/users/dave');
const john = await axios.get('https://api.github.com/users/john');
const html = `
<h1>${dave.name}</h1>
<h1>${john.name}</h1>
`;
}
in here I await axios.get()
and when that comes back I am going to await the second axios again.
That is kind of slow, we don’t want to do that, so what we can do is we can simply await Promise.all()
and by passingPromise.all()
to the other 2 promises, we sort of make one big mega promise that we can await for both of them to finish.
That was great but, if you have seen any examples online, the error handlings start to ugly it up. So let’s look at a couple of options that we can use for actually working with error handling.
Error handling
- TRY/CATCH – this is probably likely what you have seen online, so just wrap everything in the try/catch and you gonna be nice and safe. The way it looks is:
aync function displayData() {
try {
const dave = await axios.get('https://api.github.com/users/dave');
console.log(data) //Work with Data
} catch (err) {
console.log(err); //Handle Error
}
}
You have an async function, you give yourself a try, write all your code inside that TRY, and then if anything happens inside that TRY, you catch the error in your catch(err)
and you deal with that accordingly.
- HIGHER ORDER FUNCTION where you can chain a
.catch()
on async functions. So this is little bit more complicated. Let’s walk through an example
// Create a function without any arror handling
aync function displayData() {
//do something that errors out
const dave = await axios.get('https://nothing.com');
}
you’ve got a function displayData()
where I don’t care about error handling, I assume that everything works correctly and great, then I’m going to await something that maybe gives me a 404 and it’s going to break because no data came back, this could be any error that AXIOS might throw to you.
Now you create a higher-order function called handleError(err)
that takes as an argument the actual function and then from that you return a new function, you basically return the same function but with a catch()
// make a function to handle that error
function handleEror(fn) {
return function(...params) {
return fn(...params).catch(function(err) {
// do something with the error!
console.error('Error', err);
});
}
}
The same function in one line using ES6:
const handleError = fn => (...params) => fn(...params).catch(console.error);
And then what you do is you just pass your unsafe function displayData
to handleError(displayData)
// Wrap it in a HOC
const safeDisplayData = handleError(displayData);
safeDisplayData();
- HANDLE THE ERROR WHEN YOU CALL IT, sometimes you need to handle the error when you call it because you say “This is a special case” if there’s an error here I need you to handle it in a different way, so it’s pretty simple, you make your async function called
loadData()
and maybe an error happens and when you call it, you can just chain.catch()
on the end and then deal with the error.
async function loadData() {
const dave = await axios.get('....');
}
loadData().catch(dealWithErrors);
Summary
In JavaScript, it is much cleaner to use async/await than to use promises with .then()
and .catch()
, async/await is a great way to deal with asynchronous behaviour and an excellent option if you find yourself writing long, complicated waterfalls of .then()
statements.