What About Asynchronous Callbacks?

Anthony Alicea
A free video tutorial from Anthony Alicea
Software Developer, Architect, and UX Designer
4.6 instructor rating • 8 courses • 278,048 students

Learn more from the full course

JavaScript: Understanding the Weird Parts

An advanced JavaScript course for everyone! Scope, closures, prototypes, 'this', build your own framework, and more.

11:26:43 of on-demand video • Updated September 2020

  • Grasp how Javascript works and it's fundamental concepts
  • Write solid, good Javascript code
  • Understand advanced concepts such as closures, prototypal inheritance, IIFEs, and much more.
  • Drastically improve your ability to debug problems in Javascript.
  • Avoid common pitfalls and mistakes other Javascript coders make
  • Understand the source code of popular Javascript frameworks
  • Build your own Javascript framework or library
English Before we finish up this section, let's go back to a question that many of you are likely asking. We've been talking about all this synchronicity in JavaScript, how things are executed synchronously. But what about asynchronous callbacks? Many of you may have used these, or perhaps have heard of them. Well, first of all. Big word alert. What do we mean by asynchronous? Asynchronous simply means more than one at a time. So we may be dealing with code that's executing and that starts off some other code to execute, and that may start other code executing and all of those pieces of code are actually executing within the engine at the same time. But JavaScript, as we've said, is synchronous. It doesn't execute asynchronously. It executes code a line at a time. Yet there are things like click events or you can go off and get data in JavaScript and get it back. Where you have callback functions that run when that event is complete, when that action is complete. So since JavaScript is synchronous, how is this handling those asynchronous events? Well, first of all, we need to think about the JavaScript engine itself. When we're talking about running JavaScript, and the JavaScript engine itself, we understand that it doesn't exist by itself inside, for example, an Internet browser. There are other elements. There are other engines and running pieces of code that are happening outside the JavaScript engine that runs JavaScript when you load it into the browser. So there's things like the rendering engine that actually renders or prints or paints to the screen whatever the web page is that you're looking at. Or there's elements of the browser that have to do with going out and getting HTTP request responses, so going out, for example, and getting data. The JavaScript engine has hooks where it can talk to the rendering engine and change what the web page looks like, or go out and request data. But all that is running, while it may be running asynchronously meaning that the rendering engine and the JavaScript engine and request are running asynchronously inside the browser, what's happening inside just the JavaScript engine is synchronous. So when we asynchronously go out and make a request, or we say, let's run a function when someone clicks on a button, what happens? Because that is being handled asynchronously. Other parts of the browser are running and looking at that code while the JavaScript code is still running. All right, so let's take a look. We've already learned about the execution stack, that we have these execution contexts that are being created. And as functions are being called, they're being run stacked on top of each other. And as they finish, they leave the stack. There's another, however, list that sits inside the JavaScript engine called the event queue. And this is full of events, notifications of events, that might be happening. So when the browser, somewhere outside the JavaScript engine, has an event that inside the JavaScript engine we want to be notified of, it gets placed on the queue. And whether or not we actually have a function that needs to respond to it, well, we can listen for that event and have that function handle that event, but either way the event gets placed on the queue. So a click event, for example, if someone clicks on the screen. Now what happens if I have a function that's supposed to respond to that click event or maybe another event happens while code is running, like I went out and got data and that code went out to the browser. The browser went and got the data while my code kept running and now it's finished. Well, what happens is that event queue gets looked at by JavaScript when the execution stack is empty. So let's say my function b finishes and now it's going to run function a and finish whatever is going on there. And then when that finishes, it keeps going and finishes whatever execution is at the global level. And when the stack is empty, then JavaScript periodically looks at the event queue. It waits for something to be there. And if something is there, it looks to see if a particular function should be run when that event was triggered. So it sees a click event, it processes that click event and knows, hey, there's a function that needs to be run for that event. So it creates the execution context for whatever function when that event happened. And so that event is processed and the next item in the queue moves up, and so on and so forth. But again, the event queue won't be processed until the execution stack is empty, until JavaScript is finished running all of that other code line by line. So it isn't really asynchronous. What's happening is the browser asynchronously is putting things into the event queue, but the code that is running is still running line by line. And then when this is empty, when the execution contexts are all gone, all finished, then it processes the events. It waits for them and sees an event. And if an event causes a function to be created and executed, then there it is. It will appear on the execution stack and run like normal. All right, let's see some code so we can see what this looks like. So I have some code here to show what happens as far as how JavaScript handles asynchronous callbacks. I have a function here that essentially simulates a function or an action that takes a long time. So this while loop is just going to wait three seconds. It'll force this function to take three seconds to finish running. And then when it's finished, it'll console.log it. I also have a function that's my clickHandler. So I'm going to listen for a browser event. I'm going to listen for a click event to appear in that event queue and I'm gonna run the clickHandler function. That's what this line does. I'm listening for click, and I'll run this clickHandler function when the JavaScript engine decides to go look at and process the event queue. So then I run this function, the long one, and I'm gonna click around on the page while this three second function is running, to see what happens and then I'm going to console.log and I'm completely finished. So I should see all of these three console.logs. But the question is, if I run the page and click while this function is running, the one that takes three seconds, in what order will these three console.logs appear? So I'm gonna run this in my browser, and you'll notice it takes three seconds for the page to finish loading. That's that long-running function. And because I didn't click anything, notice that I have finished my function and then finished the execution and that's based on how we understand it. This function, when invoked here, was placed on the execution stack, an execution context was created. It's running. When it finished, it outputted that. Then this left the execution stack and the global code continued running, and that's what we see in that order. Now if I refresh this page and click while the long-running function is running, while it's executing, what should happen? Well, the browser is going to place that click event on the event queue, but when is JavaScript going to look at that event queue and process it? I'll refresh, click. And there we go. Are you surprised? Notice that the function completed, and the global code completed, before it went off and created an execution context for that clickHandler function. Why? Because the JavaScript engine won't look at the event queue until the stack is empty. So that means long-running functions can actually interrupt events being handled. But this how JavaScript synchronously is dealing with the fact that asynchronous events are happening, that elsewhere simultaneously in the browser in this example, things are happening that then complete that JavaScript needs to know about. So all it does, it just keeps running its normal code, and when that's all done, it will then go and look at the event queue. And if it's already done, then it will just continue to watch that event queue in the loop, the event loop. That's what that's called, the continuous check. And then when it sees something, if there's supposed to be a function, if there's a handler, if there's a listener that's supposed to run when that event appears in the event queue, it will run it. So that's how JavaScript, though synchronous, deals with asynchronous events. Any events that happen outside of the engine get placed into that queue, and if the execution stack is empty, if JavaScript isn't working on anything else currently, it'll process those events. It will process the events in the order they happened. So if a click event happens and then an HTTP event, it will process the click first and then run that function, complete that function, then start looking at the queue again. All right, so again, asynchronous callbacks are possible in JavaScript. But the asynchronous part is really about what's happening outside the JavaScript engine. And JavaScript, via this event loop, via this list of events that are happening, when it's ready, will look at events and process them, but it does so synchronously.