The problems with callback in async JS

Okay, in this section we're going to get started on building a small clone of Yelp. Yelp is an app that helps people find businesses, mainly restaurants around them.

A great way to do that is to have a way to detect where the user is located. And many apps have that feature, where they detect their users to be able to easily find places around them. This feature is called geolocation and it's a tool available on the window object. We can get it by saying:

navigator.geolocation;

Geolocation give us a number of helpful methods to get the users current position in the world, specifically one called getCurrentPosition:

navigator.geolocation.getCurrentPosition();

And getCurrentPosition accepts a callback function, which will be given the position data when it is retrieved, so for the callback, we can use whatever function we like, a function declaration or an arrow function. I'll use an arrow function, which will be passed the position data and for now we'll just console.log it:

navigator.geolocation.getCurrentPosition((position) => {
  console.log(position);
});

And just to see when this process is done, we'll add a console.log on the line below getCurrentPosition, with just the text 'done':

navigator.geolocation.getCurrentPosition((position) => {
  console.log(position);
});
console.log("done");

Simple enough. And when we run this code, we have to provide permission for getCurrentPosition to get our location. And we will approve that, but we see when the response comes back, we get something strange. Instead of getting the position data first, and then seeing done. We see 'done' first and then the position sometime later. And this happens virtually every time.

So what is happening? Usually when we put a second line of code after the first one, the expectation is that second line runs later, right?

Async code in JS

This is a clear example of asynchronous code, which is a complex-sounding word for a simple idea, that in JavaScript as one line of code is being executed, it can still move forward in our program and run the code that follows.

Good part about async code

This might seem like a problem that we have to resolve somehow. But in fact we've dealt with asynchronous code before without you likely being aware of it. You already have an understanding of how it works. Two examples of async code that we've encountered before are the setTimeout, where we wait a certain period of time before executing some code as well as with event listeners, namely addEventListener. In both cases, we saw that we could use both of these functions without it stopping our entire program. That's a good thing. People wouldn't want to use our app if it couldn't continue running until a certain event took place, like a mouse click or for a timer to finish. In other words, async code is non-blocking and that's a very good thing. Our program can continue to work to while JavaScript is doing things like fetching the user's position.

Now that we understand the benefit of async code a bit better and recognize that we've already been using it, let's take a look at how this async operation is handled with getCurrentPosition:

getCurrentPosition does what it needs to get the user's position and that takes a certain period of time, and when it gets that position data, it passes it to the callback function we've provided. And then we can do with the data what we like.

But the problem as we saw with the callback pattern is that we couldn't expect code after the function to run afterwards. So what can we do? If we want any code to run afterwards, like our 'done' console.log, we have to put it in the callback too:

navigator.geolocation.getCurrentPosition((position) => {
  console.log(position);
  console.log("done");
});

This might seem fine at first, but try to imagine that the rest of our program depends on the position data. All of our code must be stuffed into this callback function. Not a great way to organize our code. That's one.

Also, try to imagine if we have multiple asynchronous operations that we have to perform after getting the position. Many other async operations use this callback-based approach, so say if we wanted to get restaurants based off of the position we might have a structure that looks like this:

navigator.geolocation.getCurrentPosition((position) => {
  console.log(position);
  getRestaurants(position, (restaurants) => {
    console.log(restaurants);
    console.log("done");
  });
});

This function getRestaurants doesn't exist, but as you'll soon see in the next project, we could easily create one. What's the problem here? Our program is poorly organized since it now has to be stuffed into the last callback as well as little ability to manage it. This highly nested structure that emerges with multiple callbacks is one developers often refer to as callback-hell.

And the final problem with callbacks, which has long been standard for handling asynchronous operations in JavaScript,is that when using multiple callback based functions, we create this hierarchy of functions that are dependent on one another to resolve successfully. Try to imagine what we would do if there was an error in the first callback? On top of that we don't get any information about the execution of functions, we just deal with the data whenever it comes back and hopefully our program doesn't break in the process. This problem is called the inversion of control problem. We don't have control of our program, we are giving it to these callbacks and just hoping that they resolve as they should.

Fortunately using some newer JavaScript features, there are some better ways to handle important asynchronous code, which we'll cover in the next video.

Before then, write some basic code with the two other examples of async code that I mentioned, setTimeout and addEventListener and see how they work in a non synchronous manner yet without blocking the code that follows it.