- Establish a metaphor for synchronous versus asynchronous work
- Describe a synchronous code bloc
- Describe an asynchronous code bloc
- Identify a synchronous code bloc
- Identify an asynchronous code bloc
Browsers have to manage a lot. They're animating a gif
, they're displaying
text, they're listening for clicks and scrolls, they're streaming a SoundCloud
demo in a background tab, and they're running JavaScript programs.
To do all that work efficiently, browsers use an asynchronous execution model. That's a fancy way of saying "they do little bits of lots of tasks until the tasks are done."
In this lesson we'll build a foundation of understanding around the asynchronous execution model of JavaScript.
Let's imagine a chef in a kitchen preparing a big meal. There's only one chef in this kitchen. The chef could prepare a turkey, then prepare some potatoes, then prepare some bread, then prepare green beans, and then serve it.
Our diners would be treated to turkey, bread, green beans, and potatoes! This is not the goal. This meal was prepared in a synchronous model: one-thing-after-the-other. Whatever happened "blocked" the rest of things that were waiting for work.
Instead, our chef should move between each of these tasks quickly. The chef should use the asynchronous execution model browsers use. They should stuff the Turkey, they should measure the ingredients for the bread, they should peel the potatoes, etc. in a loop, as fast as possible so that all the tasks seem to be advancing at the same time. If the chef were to adopt this asynchronous model of work, the diners would be treated to piping-hot turkey, steaming potatoes, soft bread, and a fresh green beans.
So far in JavaScript, we've written synchronous code and the execution model didn't matter.
let sum = 1 + 1; // Line 1
let lis = document.querySelectorAll("li"); // Line 2
In this case, when we hit the definition of sum
, this work doesn't rely on
any "questionable" or "unknowably long" process. As soon as the work of Line 1
is done, JavaScript will then go to work finding elements and assigning them
to lis
in Line 2.
But let's consider a "blocking" operation. Imagine we had a synchronous function
called synchronousFetch("URL STRING")
that fetches data from the network.
let tooMuchData = synchronousFetch("http://genome.example.com/..."); // Line 1
let lis = document.querySelectorAll("li"); // Line 2
console.log(tooMuchData);
That work in Line 1 could take a long time (e.g. slow network), or might fail (e.g. failed login), or might retrieve a huge amount of data (e.g. The Human Genome).
It's possible that the let lis
in Line 2 will never execute! While
JavaScript is executing synchronousFetch
it will not be able to animate gifs,
you won't be able to open a new tab, it will stop streaming SoundCoud, it will
appear "locked up." Recall our chef metaphor: while the chef prepares the bread,
the mashed potatoes grow cold and the boiled turkey congeals. Gross.
Asynchronous code in JavaScript looks a lot like event handlers. And if we think about it, that makes sense. You tell JavaScript:
Hey, do this thing. And then go do whatever maintenance you need: animate that gif, play some audio from SoundCloud, whatever. But when that first thing has an "I'm done" event, go back to it and do some work that I defined in a function when I called it.
Let's imagine a function called asynchronousFetch
that takes as arguments:
- A URL String
- An arrow function that will have the fetched data passed into it as its
first argument when the
asynchronousFetch
work is done
asynchronousFetch("http://genome.example.com/...", tonOfGeneticData => sequenceClone(tonOfGeneticData)); // Line 1
let lis = document.querySelectorAll("li"); // Line 2
In this case, JavaScript starts the asynchronousFetch
in Line 1, and then
sets lis
in Line 2. Some time later (who knows how long?), the fetch of data
finishes and that data is passed into the "callback" function as
tonOfGeneticData
โ back on Line 1.
Most asynchronous functions in JavaScript have this quality of "being passed a callback function." It's a helpful tool for spotting asynchronous code "in the wild."
Let's try seeing how synchronous versus asynchronous works in real JavaScript code.
As we have experienced in JavaScript, our code executes top-to-bottom, left-to-right.
function getData(){
console.log("2. Returning instantly available data.")
return [{name: "Dobby the House-Elf"}, {name: "Nagini"}]
}
function main(){
console.log("1. Starting Script")
const data = getData()
console.log(`3. Data is currently ${JSON.stringify(data)}`)
console.log("4. Script Ended")
}
main();
We can copy and paste this into a DevTools console to see the result. It matches our default model of "how code runs."
The easiest asynchronous wrapper function is window.setTimeout()
. It takes as
arguments:
- a
Function
(the "callback" function) - a
Number
representing milliseconds
The setTimeout()
will wait the number of milliseconds and then execute the
callback.
setTimeout(() => console.log('Hello World!'), 2000)
This says "Hello World!"... in 2 seconds. Try it out in the DevTools console!
Since this code is in an asynchronous container, JavaScript can do other work and come back when the work "on the back-burner is done." If JavaScript didn't have an asynchronous model, while you waited those 2 seconds, no gifs would animate, streaming audio might stall. Asynchronous execution makes browsers the exceedingly useful tools they are.
What do you think the output will be here?
setTimeout(() => console.log('Hello World!'), 2000)
console.log("No, me first")
Sure enough:
No, me first
Hello World!
JavaScript is so committed to trying to squeeze in work when it gets a chance that this has the exact same output!
setTimeout(() => console.log('Hello World!'), 0) // 0 Milliseconds!!
console.log("No, me first")
The browser has < 0 milliseconds (i.e. nanoseconds) to see if it can find any work to do!
JavaScript in the browser has an asynchronous execution model. This fact has little impact when you're writing simple code, but when you start doing work that might block the browser you'll need to leverage asynchronous functions. Remember, these functions can be surprising and nearly every JavaScript developer sooner or later forgets to reckon with asynchrony.
While working asynchronously can be a bit of a headache for developers, it
allows JavaScript to do other work whenever it has opportunity. Important
methods which require us to think asynchronously are setTimeout()
, fetch()
,
among others.