What About Asynchronous Callbacks?

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 PartsAn 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.