When most of us are learning to code for the first time, we almost exclusively code in a synchronous manner, which essentially means whatever we want to do, we'll do it right now.
Take this add function for example. If we pass in two numbers, we expect the sum to be returned immediately.
funciton add(numOne, numTwo) {
return numOne + numTwo;
}
add(4,5) // returns 9 immediately
Last week, we talked about how JavaScript is designed to "do something". For the web, since data and other resources could be spread around, this is great! We can instruct our code to grab something from a server, the server can tell us they completed the request, and then we can go about our day.
The challenge here is that these processes are almost never synchronous. This makes sense because even in a simple page render for a website, there are a lot of different things that have to be done, like loading images. But what if an image is taking too long to load?
Synchronous Code vs Asynchronous Code
In synchronous environments, a delay like this could crash our page entirely because it might give up on waiting for that image, thus potentially ending our program. This is the point of asynchronous functions.
Asynchronous functions allow developers to build a comprehensive, executable plan for all the data we need. Because without the data we intend to present to the user, we just deliver a bad experience.
To put it into New Orleans terms, it would be like someone getting too impatient that they decide to leave out an ingredient from the holy trinity in their gumbo. Let's use asynchronous functions to help us ensure this never happens.
Making Gumbo with Asynchronous Functions
For our example, we'll use SetTimeout to replicate what it's like when our program requests something from a web server. But the main idea is that we'll be executing code at different intervals and we need to account for that.
To get started, let's create an array that holds only two ingredients from the trinity: bell peppers and onions.
const ingredients = ['bell peppers', 'onions'];
We're missing celery, but someone is making a run to the store to help us start our roux. But let's say we get impatient and start to make our roux before our friend gets back.
Well, we'll definitely have something in the pot, but it will miss a key ingredient. Might as well package that gumbo up and send it to the frozen section of the supermarket.
const ingredients = ['bell peppers', 'onions'];
const pot = []; // pot for our roux
function makeRoux () {
setTimeout(() => {
ingredients.forEach((ingredient) => {
pot.push(ingredient); // pushing each ingredient into the pot
})
console.log(pot); // will print just our two original ingredients
}, 1000);
}
function grabCelery(celery) {
setTimeout(() => {
ingredients.push(celery);
console.log(pot); // will print all the ingredients, but we're too late!
}, 2000); // won't print till after 2 seconds
};
makeRoux(); // calling makeRoux first
grabCelery('celery'); // then calling grabCelery
How can we fix this? First, we can give our grabCelery function a callback function that will execute once we can confirm that it's been added to the pot.
A callback function can be any function we want to execute once something else is complete. The function can do anything we'd like it to do, but we want it to run after we do the first thing we need to do.
function grabCelery(celery, callback) { // adding a callback
setTimeout(() => {
ingredients.push(celery);
callback()
console.log(pot); // will print all the ingredients
}, 2000);
};
What callback can we pass in to our grabCelery function? Well, obviously it's makeRoux! This way, we can ensure that our celery is returned to us before we start making our roux.
function grabCelery(celery, callback) {
setTimeout(() => {
ingredients.push(celery);
callback()
console.log(pot); // will print all the ingredients!
}, 2000); // won't print till after 2 seconds
};
grabCelery('celery', makeRoux); // passing in makeRoux as our callback
console.log(pot); // will print ['bell peppers', 'onions', 'celery']
Again, like we talked about last week, JavaScript is a single-threaded, downhill language, it doesn't do well with uncertainty and it's not very patient, so we have to use asynchronous functions to give it a little time to grab that celery or continue downloading that video even through we want to watch it now.
Conclusion
This idea of accounting for asynchronous activities is one of the most important concepts you can learn in JavaScript. While our example above is perhaps a little too simple, the main takeaway is that we have to tell our functions how to behave, especially when there are some factors outside of our control. In the next post, I'll talk about some specific JavaScript methods we can use to ensure asynchronous functionality.
Top comments (1)
Nice article on an important topic! Plus I now really miss Gumbo.