Transcript
  • JavaScript

The Event Loop

From the class:  The JavaScript Runtime

Before we had graphical user interfaces, we would usually just write a program, and the program might take some input, and then perform some kind of computation, and then spit out some kind of output or print things to the console. So for example, in this case, if we just took this simple application and ran it using Node.js-- just like this, nodeapp.js-- it'll print out all the iteration loops to the console, and then my program is complete. And then the program just terminates completely. It just ends.

But with a graphical user interface, like a browser or any of your desktop applications, the program stays open. And it has to respond to inputs from the user interface, like clicking a button. So in the rest of this video, I'd like to give you a better idea of how an event system works and why it works the way that it does. And I think studying it is important, because in your programs, you'll have a better idea of the order in which your code executes.

I've opened up the same application in the browser this time so we can play around with a graphical user interface, and I added this button here so we can add an event handler to it and see how it works. So in a graphical user interface, the system or the runtime needs to be able to respond to events that happen in the user interface-- for example, when I click this button, or even move the mouse all around the page.

To save some time, I went ahead and typed in the code to add an event listener for this button for the click event. So the event has a name, and we register this function handler that will get called when the button gets clicked. So this is probably nothing new. We've seen this kind of thing before.

So when I come over here and I click the button, it prints to the console that I've clicked the button. I've gone ahead and added one more event listener called Mouse Move. And what we'll do in this is just print out that we're moving the mouse and the number of times that this event has been handled.

So if we come over here, we'll now see a lot more events every single time we move the mouse. Then we could also click the button and get that event, and it'll be interspersed with all of these mouse move events. But what happens if I click that button or move the mouse, and some other part of my application code is already running? So for example, let's say that this loop takes quite a while to complete, and I click the button and move the mouse around in the meantime. Let's try that out.

Now, the key thing to note here is that our application is single threaded. And so only one thing is going to be happening at a time. So in this for loop here, what I'd like to do is to say, let's make it 1,000 times 50. So we're going to iterate 50,000 times, and then we'll come down here and say maybe the program isn't complete, but the iteration is complete.

Over in the browser, what I'll do is I'll refresh. And now the iteration is going on, but here I'm going to start clicking the button and moving the mouse around, clicking the button. And notice the iteration was still working its way through.

Now, what happened here is the button clicked. You noticed as the iteration was still happening, I was able to click the button. But then the iteration completes before the Event Handler function gets called. And we print click button to the console here, and then we start registering mouse move events as well, or handling mouse move events.

So what's happening here is I'm able to click the button or move the mouse around the page here, and the browser does indeed register these events, that they've happened. But it allows this piece of code here to complete before calling the event handlers for these different events. So somehow, it's able to queue up these events that they've happened and execute them in order without causing this code here to be interrupted or to suddenly stop running.

To help explain how this is working, I've drawn two data structures on the right here. And the top data structure is our call stack. So I'll just label that. This is our call stack.

And down beneath it is something that has different names depending upon where you look. But this is called sometimes a task queue or an event queue or something like that. But in our browser here, it's called a task queue. And it's a regular queue that you can put stuff at the front of the queue, and then you can just keep adding things to the queue this way.

In this center area here, we could represent our currently executing code.

And so let's pretend that this loop here, this code here, is running first. So what this is going to do is it has a call stack. And when this function or this code runs here, we have our anonymous function that we've seen before. It gets put onto the call stack.

Now, there's a loop that's running, and it's running in a separate thread. In other words, it's running concurrently with our program. And it's the runtime that is running this loop.

And what that loop will do is check and see whether or not the stack is empty. So it'll say is there anymore code that's executing right now? And if not, go ahead and check the task queue and load the next thing in the task queue, which will be a function. Load that into the stack and start running it.

And then on the next iteration, it'll say, is the stack empty yet? No? Then just don't do anything. But if it becomes empty, then grab the next item, the next function on the task queue, and put that into the stack and start running it.

So we have this runtime thread or a piece of code that's running in the background. And when we click that button, the runtime thread says, OK, the button has been clicked. Do we have any event handlers for that event? And it turns out we do. We have this function here.

And what the runtime will do is to take that function and put it at the back of the line or the back of the queue. And I'll just call this func for our handler, or func 2.

And that function will make its way to the beginning of the line. And when it gets to the line and the stack is empty, it'll go ahead and get executed by the runtime. And that process will just continue until we close the browser. So this loop, or the event loop, is just continually checking for whether or not there's anything in our task queue, and if so and the call stack is empty, go ahead and execute the next item. So this provides a nice way for things that are happening asynchronously or concurrently, like different user interface events, to work with our single threaded code in the application.

Let's take a look at this in the debugger. I've added a couple debug statements so that we can step through and take a look at the call stack as we move through the program. I'm going to show you one trick here. We're going to go up to the Click Event Handler, and I'm going to name this function.

And that way, we can see it in the stack more easily. So even though this is an anonymous function right now and we can pass that as a value into the addeventlistener function, I can name it just like this. That will make it easier to see it in the call stack.

So over in the right, I'm going to refresh this. And that will drop us back into our debug statement once it reloads. Notice our call stack here at this point. So we've started running this program, and we're about to jump into this loop. And the call stack has this anonymous function, and that's the one that is first put on the stack to run our program.

Now, what I'll do is I'll press play. And then while the iteration is happening, I'll click this button a few times. And you're not seeing it highlight, but I am actually clicking the button.

And now once our iteration has completed, we get dropped into the second debugger statement inside of the event listener. But take a look at the call stack now. It's completely empty of the previous anonymous function. And now the only thing on it is this onclick function. So that's this function here. And that's the code that's currently executing.

Each time a new function from the task queue gets put onto the call stack, the call stack just starts with that event handler, and the previous call stack is emptied out. If I press play, this event handler code will finish executing, and then the call stack will be empty again. And the next queue or the next function in the task queue can then be put on to the call stack and run again. So you can actually see down there in the console, there was another event that was handled as well, which was the mouse move event.

So the event loop combined with a task queue provides a nice way to be able to not freeze up the entire user interface while our code is running, but to queue up those events as functions on the task queue and then execute them sequentially. So it gives us a nice way to organize our applications and to reason about the order in which things are happening.

I think it's kind of cool to note that the event loop has been around for quite awhile. And if you go over to Wikipedia, you can learn about its history. You can see that it's been around since some pretty early operating systems, and has been a core design principle for graphical user interfaces. And if you read its description here, you see that in general, what it is is a way to dispatch events, or any kind of message, really, into a program in a sequential way.

So I think this is pretty cool. And you can go and check out some of the history of how we got this event loop-based system in our browsers.