Someone on twitter said she had trouble with the concept of await and promises. I thought I would give a link to a good tutorial. I found great technical articles, but not so much about the concept. If you want technical details you should scan a few google results and pick one that works for you. You can find dry (works for me), or tutorial form, or verbose “for Dummies” articles depending on your learning method. There are MANY others.
I will try to avoid repeating them and cover the why & what and leave the how to them.
One Thing At A Time
A browser can only actively do one thing at a time. All of this is the same for NodeJS applications. If you could tell the browser to actively wait for the user to press a key, it would not be able to react to the mouse during that time.
There are many times your code needs to wait for something but instead of actively waiting, you give the browser a callback. This lets it do other things while you wait.
myInput.addEventListener(‘keydown’, userPressedAKey)
myDiv.addEventListener(‘mousemove’, userMovedTheMouse)
The browser can draw things, fetch data, handle window resizing and anything else it wants until the user does something with the keyboard or mouse. At that point, the browser gives you control by calling your function userPressedAKey() or userMovedTheMouse(). The browser cannot do anything else while you have control so you don’t want to take long. (we’ll get back to that later).
You can also let the browser do what it wants for a period of time before giving control back to you
setTimeout(afterFiveSeconds,5000);
The browser is free to do what it wants for 5 seconds, then it calls your function afterFiveSeconds().
You could actively wait 5 seconds with
const startTime = Date.now();
while(!isFiveSecondsUp(startTime)) {}
afterFiveSeconds()
But the browser could not do anything while you spin. You can give the browser a function to call for a wide variety of things that take time
- User actions (mouse & keyboard events)
- API requests (XMLHttpRequest)
- IndexedDB
Promise
When you write code that takes a while, you could implement an event listener mechanism like DOM addEventListener(). Or you could let the caller provide callbacks for when you are done, like setTimeout(). Nothing you do should take much time since the browser (or nodejs) can’t do anything while you work.
You can write a function to get the latest weather from the server and use callbacks:
getLatestWeather(onSuccess, onFail).
This might be called every 30 minutes from a timer, and when the user presses ‘w’ and when the user clicks a button. All of those would provide callback functions to do something when new weather data is available and another if something fails. Until onSuccess or onFail is called, the browser can do whatever it wants. It is called an asynchronous function because the browser can do other things while it works and you will get to do something when it finishes.
getLatestWeather() doesn’t return anything. It just agrees to call one of the functions you provide when it’s done – it promises to call one of them.
A Promise object flips control. Instead of the caller providing 2 callbacks, the slow function returns a Promise with 2 functions. It promises to call one of them if it succeeds and the other if it fails.
One of the problems with understanding promises is naming. What do resolve and reject mean?
return new Promise ((resolve, reject) => {
// requrest the latest weather
// do something with resolve and reject?
})
Resolve and reject are callback functions the Promise gives you. If you write the function getLatestWeather(), you promise to call resolve() if the function succeeds(), or reject() if it fails. This could be written as
return new Promise ((onWeatherSuccess, onWeatherFail) => {
// request the latest weather.
if (itWorked) {
onWeatherSuccess(result);
} else {
onWeatherFail(err);
}
})
Things change if you are using getLatestWeather(). Getting the result was pretty easy with callbacks
getLatestWeather(onSuccess, onFail)
You just implemented onSuccess() and onFail() to do whatever you want. One of them will be called at some point in the future.
Now you have this Promise object and you haven’t specified any callbacks.
const weatherUpdatePromise = getLatestWeather()
How do you know when it’s done now? You provide your callbacks to the promise object with the then() and catch() functions. It’s not usually written like this, but you can write
weatherUpdatePromise.then(onSuccess);
weatherUpdatePromise.catch(onFail);
then() is how you register your callback for when the asynchronous function succeeds. catch() is how you register a callback for when it fails.
That’s the basics: 2 callbacks but they are returned from an asynchronous function rather than passed to it.
There are many advantages to doing it this way and many other things you can do with Promise objects but I’ll leave it to other articles to explain those details.
Async & Await
Code can get out of control (ugly) when you start using all of the power of promises. Async and await came along to pretty it up (and make it readable).
Async simply means a function returns a promise. These 2 functions can be identical in implementation
async getLatestWeatherAsync() {...}
getLatestWeather() {...}
They can both make the same API request and return the same Promise and be the same in every way. One of them can just call the other. The difference is one of them is telling the browser it will return a promise while the other doesn’t say that (not entirely true, but that’s the idea. )
When the browser knows a function returns a Promise, it can do some magic so the caller doesn’t have to create a callback. The caller can still implement a callback and use the result just like before.
const weatherPromise = await getLatestWeatherAsync();
weatherPromise.then(weather =>{
const temperature = weather.temperature;
// do something else
});
Or it can use await
const weather = await getLatestWeatherAsync();
const temperature = weather.temperature;
// do something else
Conceptually the browser (or NodeJS) does 2 things
- Create a hidden callback and promise.then(hiddenCallback)
- Waits until the getLatestWeatherAsync() is done before executing the next line
You could have done the same thing.
What about the fail callback? Async/await handles the fail callback with the standard javascript exception mechanism
try {
const weather = await getLatestWeatherAsync();
const temperature = weather.temperature;
} catch (err) {
// getLatestWeatherAync error
}
Summary
That’s the basic idea of promise and async/wait: 2 callbacks.
There is a lot more to them. Once you understand these basics you can start using the full power of them. Or not – more control is not always needed.
Let me know on twitter if you have comments, questions, complaints, or suggestions for another post.