What do you want to learn?
Skip to main content
by Wes Higbee
Start CourseBookmarkAdd to Channel
Table of contents
Who This Course Is For
How to Use This Course
Learning is doing. You will not learn by watching this course. You will not learn by reading through the supporting materials. You'll learn by practicing, and you'll ultimately learn by applying what we're talking about in this course to real development projects that you work on. I've created quite a few challenges in this course where I'm going to ask you to try something on your own, and then come back and I'll explain how I approach the problem. I really encourage you to take the time to work on these challenges. In addition to these challenges, look for opportunities to apply these ideas in real code that you're working on. Find existing code that has problems that are synonymous with what we're covering in this course, refactor that code, and see what you think of the result. It doesn't mean you have to push the refactoring into production, but if you take the time to apply this to real code you've authored, to real problems you've faced, you'll learn these approaches in no time.
Links to Examples
To work through the examples in this course, I have a bunch of Plunkers created, and I've created the links here for you as a reference on the slide. As I work through the various different examples, you can refer back to this set of links if you want to follow along. Make sure you use this set of links, there are a few Plunkers that were out of date that I might have used in the actual course, these are the latest versions of each of these. Sometimes I made some changes in the course, and then saved those as new Plunkers, so just refer to this set, and try to refer to them by the demo name here, which is the black part, and then the Plunker link below. Throughout the course I'm using the OpenWeatherMap for several of the examples to demonstrate making web requests. If you'd like to follow along with those you'll need to sign up for an account, and get your own API key. The API key I use in the course will be expired by the time the course comes out, so you won't be able to use that. You'll need to get your own API key if you want to try out those examples, and then you'll just need to replace the API key in the sample.
Reasoning Explicitly About When Code Executes
The Event Loop
How to Avoid Blocking
Run to Completion
Challenge - Run to Completion
Animated Walkthrough of When Code Executes
Mentality: Little Programs
Exercises and Examples
Sources of Async
I find it extremely helpful to think of the different sources of asynchronicity. In other words, the different things that are likely to push work onto the queue in the event loop that will eventually be executed later on. Back in the last module, we went through the animation of work being pushed into the queue. For example, when a user clicks on the button on the page in the sample, the button clicked handler is pushed onto the queue, so this is a source of asynchronicity. Something is now lined up to be executed later on. Later on in the animation when the request for the current weather completed, the weatherSuccess callback was pushed onto the queue to be executed later on. Later on when the request for the 5-day forecast completed, and it failed, the failure callback was pushed on to the queue. So these are some sources of asynchronicity. And we just took a look at the first two of these, user interactions and network I/O. Some other common sources include disk I/O, so reading files from the disk, or writing files to the disk, interprocess communication, for example if you use web workers behind the scenes you could have a separate process where your web worker is running, and as you communicate with your web worker, or it communicates back to you, where you would be the code that's running in your browser tab, as you communicate back and forth that's interprocess communication, which is another source of asynchronicity. Processing that message when it comes back will be pushed onto the queue to happen later on. And timers are another source of asynchronicity, scheduling some work to happen later on. Let's work through some examples of each of these to help you to be better prepared to watch out for these as sources of asynchronicity.
Source: User Interactions
First let's take a look at some user interaction examples. Now we've already seen button clicking, so I'll avoid that. Instead let's take a look at this Plunker I have set up. You can go ahead and copy this link if you'd like to follow along. I embedded everything in the index.html page just so we could stay in one file. So let's walk through this. I have some styles set up, and when we run this sample, you'll see there's a red box and then there are two empty boxes. These are the styles to set that up, so let's collapse those because those aren't important. And then I have the HTML to define this sample here. We have a box that's draggable, and we have two boxes that are droppable, so we can drop things on these empty boxes. First class support for drag and drop was added in HTML5. Now you'll have to check your browser to see if this is supported. Here in Chrome this example will work. Drag and drop is a great example of a multitude of sources of asynchronous behavior, code that will execute later on. And of course, this is all involved with user interaction, because users will be dragging and dropping elements on the page. So we have this red box that we click on, and grab, and drag around. I've defined it as a draggable element, and then I've also defined an event handler for when dragging starts. So when I click and drag, the dragStart event will fire, just like clicking a button on the page. And that event has a handler, this dragStart function, that's defined down below in this endline script. So let's go ahead and pull open the console, clear out the screen here. Each time I click and drag, the dragStart function will be pushed onto the queue, and will then be executed. And this function most notably prints out a message to the log saying dragStart, and it also logs out the event object. So you can see over on the right-hand side here I've started dragging this twice so there are three log entries, if I do it again we'll get another dragStart. So drag and drop is a source of asynchronicity in your applications. This example also has other dimensions of asynchronicity. Pause the video, and see if you can figure out how many different little programs we have involved in this example. Okay, let's walk through this here. I see four little programs. First off we have this whole script block. So I can collapse this, this whole script block has to execute so that we have these functions declared to be wired up to the various different events that can happen. So that's one small program. Then we have three separate events that can happen as a part of dragging and dropping. We have ondragstart, we have ondragover, and we have ondrop. So these each are three other little programs that will execute when each of these events fire. Now we've already seen dragstart here, and we've seen the event that's logged out in the console whenever we start dragging. We also have ondragover, this is the third little program. And you'll see here this is wired up to the dragover function down below. When I click and drag we log dragStart, and then when I drag over the console goes crazy as we log out all the different coordinates as we're dragging over one of these elements that has an event handler wired up for ondragover. And we're logging out the coordinates of where we're dragging that. I'll clear this out so we can run through one more example here to cover the last little program, which is when we drop. When I click this red square, drag it over and drop it, you'll see one last console log entry where we drop the drag element, and you'll also notice the red box is now inside of the first empty box. I can then drag it down below to the other box, I can drag it back and forth. So again, four small little programs in this example. So whenever you have user interactions with your page, look for the various different events that will be firing off as a result of those user interactions, and you'll find asynchronous seams around those event listeners that will be little programs that you can start to reason about as happening later on, and each running to completion before any other work can execute.
Next I want to talk about timers. Yes, I'm jumping ahead. You've probably used setTimeout or setInterval before to schedule some work to happen later on. On the Node.js side of things, in addition to setTimeout and setInterval, there's also setImmediate, and then there's process.nextTick, so there's a couple of extra ways to schedule work to be done in the future with Node.js. Let's take a look at an example with setTimeout. I have another Plunker here you can pull down. And one key thing I want to point out about the HTML page is that the script is loaded in the head of the HTML page, which means it will start executing before the rest of the body is available. Specifically I want you to note that there is a div tag with an id of content here because we'll be manipulating this in our script. If you hop over to the script.js file, you'll notice at the top I have this line of code that sets the content of that content div, and it injects this text, Main content here from JS. Now, the interesting thing is if you look on the right-hand side you don't see that text anywhere. So why is it that we're not setting the innerHTML on this content div? Let's give ourselves some space here, and let's pull open the developer tools. Okay, and let's go ahead and collapse the File view. All right, if we add a space that will trigger the rerunning of our script, and you'll see we're getting an error over in the console. The error is that we cannot set the property innerHTML of null. So for some reason our document.getElementById is not working. And that again is because, over in the index page, this script is loaded in the head of the HTML document, that means it will start executing before the body is available. And in this case, we've hit a race condition where our content div is not yet available, so we can't find it to set its innerHTML. So one thing we could do, now you wouldn't do this in reality, but what we could do is add some sort of delay and wait for this content div to be available, and we can use setTimeout to do that. So we can come down here and we can call setTimeout, and let's go ahead and stop this so we're not re-executing it, and we can pass a function to setTimeout that will be called later on. This function, let's just pass it, and let's call it in 100 ms. Let's go ahead and yank our line of code and put it inside of that function, so this will get deferred and executed later on. Now let's open the console back up, clear it out, and let's run our script. And now you can see our script is working. We have our main content added in. Now in this case we didn't check to see if the content div was available, that's something you probably want to use if you want to use this in reality. Though, in reality you probably would listen for the page to be loaded, instead of setting your own timeout, and waiting to see if you can access an element. Nonetheless, you can see here, by scheduling some work to be done later on, the body of the page will load, our content div will be available then when this function executes to set the innerHTML on that content div. So when you call setTimeout, when the timer elapses, the 100 ms here, once that time is up, this function that you provide will be pushed onto the queue of the event loop, and then once whatever is running is done, and everything in front of this function in the queue is processed, then this function will kick off. So this highlights an important aspect of setTimeout that's often overlooked. There's no guarantee that our function will be called at exactly 100 ms from now. In 100 ms the function will be pushed onto the queue, but if there's a lot of work in front of it it won't be called until all that work is done. If you want another analogy to think about this, when you put things on your calendar to do on a day in the future, that's like calling setTimeout, and reminding yourself you have something to do in a certain amount of time, a certain number of days from now. That doesn't guarantee that you will get that work done as the very first thing that day, it doesn't even mean you'll get that work done that day at all. You might have a full day ahead of yourself that day, and you may never get to the work that you scheduled.
Timer Delay Gotchas
All right, it's time for a challenge. I've got a Plunker up here that you can pull down. I want you to pull this down, and take a look at it. And the very first thing I want you to do is to figure out how many little programs are involved here. And I'll give you a hint, don't worry about the index.html page, all this example does, the index.html page runs a script, there's nothing really going on with the UI. So go ahead and pause the video, take a look at this, and find how many little programs you think there are. This one's a little bit tricky, there are two little programs here. First off, we have one program for the entire script, there's always one program for the script itself. All these variables you set up here at the top, invoking our testDelay function for the very first time, and actually the execution of this testDelay function for the first pass, that's all one program. It's important to note, the very first time testDelay is called, it is not a separate little program. It's called synchronously as a part of the entire script. Let's talk about the behavior of this program. This program tests the actual delay of setTimeout versus the requested delay. So we ask for a specific delay here, and it's set to 0 right now, we measure the difference between that delay and the actual amount of time it takes before our function is invoked, so this function inside of here. And by the way this is just a plain vanilla function using fat arrow syntax, I'll switch this over just so that it's not confusing. Now when we run this, and because I changed the code here this ran again, a dialog will pop up, and it will tell us what the requested delay was, 0 in this case, versus the actual average delay. And we run this 1000 times right now, so this is the average of 1000 iterations. So you can see the difference is rather interesting. We're asking for 0, but we're actually getting almost 5 ms of delay. Okay, back to counting the number of little programs. So we have one right now for everything, including the first call to testDelay. This testDelay function then on the first call will check to see if we've passed the total number of repetitions we'd like to perform. Right now we're requesting to do 1000 repetitions. And actually as a side note we do one more than the total number of repetitions, we should be starting our repetitions out at 1 if we only want to do the total number. Because we have not yet hit the total number, because we're just starting out, then we continue on with the rest of our program, and this last part is what runs each individual test. We capture the current time, and then we set a timeout, and we specify the delay we'd like, that's our requested delay, maybe it would make even more sense to say requestedDelay here. I'm going to stop the program here. After the requestedDelay expires this function is executed, and the very first thing it does, it takes the date right now, subtracts off the start time, that produces the difference in milliseconds of the actual delay, so we add that to our total actual delay. If you want we could add another variable here, which says actualDelay for the run we're talking about, not the total, and we could extract out a variable here, and add actualDelay. We could do this instead if this makes a little more sense to you, we could calculate the actual delay. By the way, I'm using ES6 syntax here, instead of var I'm using let and const, and actually this could be const. Const is declaring a variable that will not change, let defines a variable that can change. For all intents and purposes just think of const and let as var if you're not familiar with those. So we add each run's actualDelay to the totalActualDelay, and then we call the testDelay function again. So this is a recursive function in a way, because after the timeout is elapsed, this function is then called again to perform the same process. So our second program is this little function right here. But it's really important to note that this second little program also contains this testDelay function as well. It synchronously makes a call to testDelay, just like our first little program does, so testDelay is a part of both of our little programs. It's kind of interesting to bend your mind that way. And thus we have two little programs in this example. Let me finish explaining this, just in case any of this example is confusing to you. So testDelay will be called after the delay, and after we've computed the actualDelay, and it will run this whole process again, and it'll keep doing this until we hit the total number of repetitions, in which case then we print out the Actual Average Delay. And the Actual Average Delay is computed by taking the totalActualDelay, and dividing by the total number of repetitions. So there are two little programs in this sample. That's important, but also important is that when you request a delay of 0 ms, you're really looking at about 4 or 5 ms, at least with this sample program. Again, we're looking at 4.6 actual average milliseconds before whatever function we pass is invoked. And if I crank this up, let's say let's change this to 10, don't make this too high or you will permanently lock up your browser window because we're running this 1000 times, but 10 ms times 1000, you can see in this case we requested 10 and our actual average delay was about 10.744. So this was much closer. Now the reason for this, is that when you say you want 0 ms, the browser is putting in 4. So if you put 4 in here instead, you'll see we requested 4 and we got about 4.6. Anything less than 4, say 2, will still come out at about 4.6, because the browser is substituting 4 whenever the value is less than 4. So be aware of that, but more importantly be aware that there's still a fractional component that's rather substantial here. And if you had a lot of work in your queue this number could balloon up exponentially. So there's one more new key takeaway here, and that's that the timer delay is not a guarantee that the function that you pass will be invoked right at that amount of time. And again, this is because a timer is just the amount of time that elapses before an item is pushed onto the queue. It still has to flow through the queue and compete with whatever's in there first, before the code will actually execute. So the delay in a way is a minimum.
Not Always Async
Okay, time for a challenge. Take a look at this sample of code, and pause the video to figure out how many little programs are involved, and then once you figure out how many little programs are involved, use that to figure out what will print out in the console. Okay, let's see what happens here. I've opened up the console, and cleared it. Let's go ahead and hit the Run button. And there's the output, is that what you expected? From where you're sitting right now, there's no way that you could have known that that's the output. So if you got it right, well guess what, no you didn't. And if you got it wrong, well guess what, no you didn't. There's no way to be right or wrong about this example, because I didn't show you what's inside this forEach function. There are many, many different ways I could have written this function that would change how the output displays. But, I'm really hoping you guessed that something else would show, aside from what actually displayed, and I'm hoping that because I primed you to think that something else would show. So that's a good thing, and it's okay because that's really what I intended. Hopefully somebody falls into the trap I kind of laid out here. We've been talking about callbacks, and we've been talking about things happening later in this course. I set up this example so we have an array of numbers, we log out start, we call this forEach, and then we log out end. And inside this forEach, I set this up to hopefully make it look like this was a callback. I even named the parameter to forEach callback, and I was hoping that you would think this would execute for each of the numbers later on, but in fact, if we open this up, all we're doing is looping over the numbers, and calling the callback with each item. And this is being done synchronously, and that's why we have start, 2, 4, 6, and then end. This is all one little program. And there's no way for you to have known if it was one, two, or many, many more without seeing this forEach implementation. And this is really, really important. And so it's time to pull up our slides and talk about another key takeaway. And that key takeaway is that just because something looks like it's asynchronous, just because there's even a parameter called callback, that doesn't mean that it's asynchronous, it may indeed be synchronous like we saw right here in this example. A good way to remember this is even if it looks like a duck, it quacks like a duck, and it waddles like a duck, that doesn't mean it's actually a duck. So be very careful when you're reading through code, especially if you're using somebody else's code, or a third party library, be careful to validate that what you think is asynchronous actually is. If we come back to the example here, if I had put a setTimeout inside of here, actually let's put the setTimeout around the callback for each item, create a function, and call the callback inside here, now we legitimately have a situation where things are executing later on, and you can see over in the output here, 2, 4, and 6 come after end. And of course if I fold this down, and if I were to have started this clip right here, things would have been completely different. And that's why I'm telling you, without seeing the forEach there's no way to have been right or wrong about the number of programs, and what would show in the console when we run this. So make sure you verify your assumptions when you're looking for the little programs in your application, especially when third-party code is involved.
Let's get back to our sources of asynchronicity. The next one on the list is network I/O. We've already covered this a little bit, so what I want to do is a bit different. I want to take that existing example we've already been talking about, and I want to show you how you can debug your program, and how that can help you figure out where your little programs are at, where your asynchronous seams are at. So what we can do here is just at the top of this script we can just type debugger. And that will stop our program execution right on this line. We'll have to open up the developer tools to do this. If we change our program then it will run again, and now we'll hit the debugger. You can see Chrome says we're paused in the debugger, let me pull this over a little bit, and make this a bit bigger, and you can see the line we've stopped on here. And of course, I can't get into all the nuisances of the Chrome debugger, but like any other debugger in the world, you have a bunch of information that's captured about the current context of your program, the current execution context. You can see Threads, you can see the Call Stack like we talked about earlier, Breakpoints, and various other breakpoints you can set to help you out. On the right hand side you can see the scope information, and you can add watches. Right now, the Call Stack says, we're in an anonymous function in index.js. We can now come up to these little arrows, and we can click on the one, or hit F11, to step into the next function call, or we can step over the next function call. So I'll use the step over, F10. We'll step over hardcoding the value of this text box, and watch that happen by the way. And then we'll call the load function, except I stepped over that, so I needed to step into that. So let's just resume the execution of our program, and start this over. Okay, let's start again, let's step over setting the city value, but let's step into the load function. Watch the Call Stack here as I step into the load function. Now you can see that the load function is pushed onto the Call Stack. This is just like the call stack I had in my animation earlier. So this is the real live call stack of your application. So you can see at this point we're still in this anonymous function for our entire script, so we're still in the little program that represents the whole script file. Now in this case we can step over getting the city value, we can step over showing the showResults of loading, and we can step over creating our URLs. And so here we are on this console.log call, so if you're wondering where and when does this execute, well, it's a part of the load function, which is a part of the little program we have that represents our entire script. So this cis how you can verify your assumptions. Step over that and we print out the log value of 1, if we come to the console we can see that. Go back to Sources tab, we can continue on debugging. And here we are setting up our weatherRequest, so if you wondered where that happens at, well, let's go ahead and step over these lines of code, and here we are in our next log statement. So we fired off that web request, if you go to the console there's nothing printed out though, so that's not complete, and no handler is fired off. We can now step over logging out 2, in the console we have 2 now, step over setting up our second request. Again, in the console though, nothing's come back for the weatherRequest, this is the previous call. So we have 1 and 2 right now. Step over, we should have 3 now, and there's 3. Okay, and you can see we're at the end of our load function here. So our load function is now done. Let's go ahead and set some breakpoints in here. Looks like I was having a little trouble setting those breakpoints. We've got one now set on line 37, get one on line 43 as well, and get one on line 49. Now we can resume the execution of our program. Boom, we hit one of the breakpoints in our weatherSuccess function. So we had a successful request, in this case. And if we look at the call stack now, there's no anonymous function down here for our index.js file. That means we are in another different little program right now. Now I will say right now, the way we're debugging is not so helpful, it would be nice to have some information about what little program we're in, but because we don't have that anonymous function like we did before we know we're in a different little program, and right now the root of this call stack is weatherSuccess. So this weatherSuccess function is actually what was pushed into the queue. We can step over here, and that means we will have printed out to the console, and will now show the weather here in the browser. So there's our current weather object in the console. And if we resume the execution of our program, boom, we hit the fiveDaySuccess for our second request. Again, if you look at the call stack, no anonymous function here, so we are in another little different program. And fiveDaySuccess is the root of that call stack. So that's clearly starting to seem different from our weatherSuccess function's little program, so this is our third little program. And we can step over the lines of code here, we'll have our 5-day forecast in the browser, and in the console you can see the five day forecast object. And if we resume the execution of our program, it looks like we never hit the failure function. So if you're ever wondering about the little programs in your program, you can use debugging, and actually look at the call stack to verify your assumptions. In the next clip I'll show you an even more helpful feature of debugging, which is asynchronous debugging.
Debugging with Async Call Stacks
Okay, one more helpful thing when it comes to debugging in the browser. If you're trying to validate your asynchronous assumptions you should check this box for Async debugging. Let me show you what this does. I've kept my existing breakpoints, and I've cleared out the console. Let's hit the Load button again to run through our program. Now because I hit the Load button we jump to the fiveDaySuccess function, it looks like the five day request came back first. Now here's what's really interesting when you check this Async box, look at the call stack now. In the last clip it was just fiveDaySuccess, now you see additional information, you see onclick, you see load, and you see XMLHttpRequest.send, and it says async. So this fiveDaySuccess function was called as a result of sending a web request, an XHR here, and that was called because of the onclick event calling our load function, which then called the send function. So this shows you where your asynchronous calls came from. This shows you where things that run later were triggered from, it's really neat. So this can show you a different dimension to our little program. In fact because I triggered this because of clicking the Load button we see a different little program than we see when we first load the page. So right now we have onclick at the base here, let's resume the execution and just skip over everything else here, and let's just reload the page instead. So let's make a change here. You see we hit our debugger now. Still at the breakpoints, let's go ahead and resume execution, we hit a breakpoint. Now this time because we reloaded our script, we don't have onclick here because we didn't click the button, this is our anonymous function, so our little program that represents our script. So this is a different dimension of a little program. As you can see now, because of the examples we've gone over, little programs can overlap with each other. We saw this back when we were estimating the actual delay versus requested delay of the setTimeout function, now we're seeing this again here. We have overlap and what can call this load function, and that makes sense because different parts of our program can call the same functions we provide from different sources. In this case the source is loading the script versus the source being clicking a button. Anyways, even in this case we can see the same call stack here, originated from this original call to load, going through sending the request, and then that successful sending of the request triggers the weatherSuccess function. Now notice this time, the current weather request completed first, so that's the first callback we hit. And then we resume execution, now we hit the success on the five day, and notice it likewise shows the original location of this, the origination of this. And you'll notice the load function is referenced on line 31, over here line 31 here is fiveDayRequest.send. In fact you can click on these different parts and jump right to those lines of code. Here's the call to load, here's the call to send, and then here's the fiveDaySuccess function. So you can click through this almost as if this were a synchronous call stack, pretty neat. Again, the Async checkbox for that.
Next up, disk I/O, and for this example we'll take a look at Node.js. This example uses the fs module that's a part of the Node.js API. I'm going to stop right here, and just ask you to take a look at this and tell me how many little programs are there? There are two little programs in this example. One for the entire script, and then we have a callback here, and that's the second part of our program. Notice this is using a special convention, just in case you're not aware of it. It's typical in Node.js for a callback to receive two objects, one the error, and one whatever the result is of the function we've called, so readFile in this case, it's the file contents. That callback also comes as the last argument to a function. Callbacks are a notorious sign that a function is asynchronous in Node.js, but just like always you can't assume that, you have to look at the implementation of the function, or look at some good documentation about it to know if in fact it is asynchronous. The Node APIs themselves, the built-in ones like the fs API, those you can all trust to be asynchronous. Now of course no trickery here with the logs, they're all inside of our second little program, but let's go ahead and run this anyways. You'll see we actually get an error back. I left this deceptively, so we could talk about errors. In this case we get an error so we abandon the processing in our callback. This is a lot like try catches in a lot of other languages. Though, with asynchronous functions when things go wrong that error has to be passed back somehow, either to a dedicated callback like we saw with the web request there's a failure callback, or in the case of Node.js it's typical for a callback to receive either an error object or an undefined parameter for the error object, and then the callback also receives the response, or the result, if things are successful. If you are curious about making this work, we can refer to the directory that this script is in with __dirname, and this will fix the problem of pathing to the mysql file. When we run this program now, we actually have the program printed out here from this mysql file.
Nodejs nextTick and setImmediate
While we are on the subject of Node.js I want to take a minute and talk about setTimeout, specifically setImmediate and process.nextTick in Node.js, which are some additional means to schedule work later on. By all means you should refer to the Node.js documentation as you learn about setImmediate and process.nextTick when you want to use it, but I do want to briefly point out that there is good documentation for it, here's the docs for setImmediate, and then under the process module you'll find the docs for process.nextTick. These are additional means of, more or less, triggering work to happen later on. So in both of these cases these are like timers. First let's take a look at process.nextTick. Let's say we set a timeout to call a function here in 0 ms, which will default to 1 ms in Node.js. And then in 1 ms, when this function is invoked, we will log out st-1. But let's also do this, let's call process.nextTick, and to process.nextTick we just pass a function. And in this one let's log out, and we'll call this nt, for nextTick, -1. Now before I run this, tell me what you think is going to happen, no wrong answers here, just think about what you think will happen here. And by the way, use your understanding of the little programs here to take a stab at this. Okay, I've opened up the terminal over on the right-hand side here, let's go ahead and run this program. So I call node and pass set.js. Interesting, right? Is this what you expected to happen? Maybe to make this a little more interesting let's make a copy of setTimeout, paste it here, also waiting for 1 ms, and one after we call process.nextTick. So this will be st-2, and this will be st-3. Let's run the program again. Before I do that though, what do you think will happen in the output? Again it looks like the nt, the nextTick, is beating out all of our setTimeouts. So it's obvious that process.nextTick is happening before all of our setTimeouts fire off. Could it be that process.nextTick is synchronous? Well, if we wanted to know that we could debug our program, or we could go ahead and log out, make a new Error, and grab the stack. Let's run this program again, make a little more room here. Actually let's clear that out and run that again so we get the full stack trace. So if we look at a stack trace here, and maybe it would be helpful to compare it to another stack trace, let's put one of the stack traces on the setTimeouts. Clear again, and run again. All right, so we have the stack trace for the nextTick here, and then the stack trace for the last setTimeout. You can tell these are different. If we put a stack trace in for the root of our script here, let's run this actually, so we have three stack traces now to look at, hopefully this isn't too confusing. So the first stack trace is for the root of our script. You can see, at the very top here we're referring to set.js on line 19. So that's where we're at right here. Then we get this stack trace for our nextTick. Ah, and these look somewhat different. If we look at our first stack trace you can see we've loaded up the module for set.js, but this is in a call stack involving this module.js file, whereas our nextTick, well, there's module.js, but then there's this next_tick call stack. So this is happening out of band with our scripts. Now behind the scenes this could be synchronous from this runMain function, but it's at least not synchronous in terms of this script. If we scroll down here, and look at the call stack for the timeout it's completely different. There's this timer.js file referenced. So we do indeed have asynchronous code being fired off with both nextTick and setTimeout, it just looks like nextTick has the upper hand. Let's comment out these call stacks, clear the screen here, and run our program again. You can see the nextTick is winning. Let's copy and paste that actually, call this nt-2, and let's see if we have 2 of these, let's see what happens now. And it looks like the nextTicks fire off in the order that they're registered. If you want to understand how nextTick works, again the documentation is the best place for that. I'll point out a few things here. First off, nextTick, all the callbacks that you register will be run at the end of the current event loop turn. That means, whatever is running right now, which is our script at this point to register all these things, once that's done, all of the things we registered with nextTick will be called, all of them. And actually that last part you have to scroll way down to the bottom to find. Note: the nextTick queue is completely drained on each pass of the event loop before additional I/O is processed. Keep that in mind. Basically, you're putting things at the front of the line. So you could think of a bathroom at a bar, or maybe the line outside of a bar, nextTick is like the bouncer that lets people cut in line, that lets the VIP people get in ahead of everybody else. The other construct in Node.js, setImmediate, that's new. All I want to say about this is just like process.nextTick, it's for running something in the future, but sooner rather than later. It's quite a bit like calling setTimeout with no delay, though technically that's not accurate for all intents and purposes, for you that's all you need to know. Also, one of the things you have as a guarantee of process.nextTick and setImmediate, all of the items that you schedule with these will be scheduled in order, so you'll have some guarantee about the order of execution, which is something you don't always have with timers. I just wanted to point these out to help you understand that there are other means of scheduling work to be run later on with Node.js, which is something I think you should be aware of, but unless you are writing some library that requires a particular order of execution in something really, really special, you'll probably stay away from process.nextTick. And the only situation you would really need setImmediate is if you want something to happen with pretty much no delay, and you want to guarantee some ordering of the various immediate callbacks you would schedule. So all of the things you call to setImmediate would be ordered, but don't try and come up with a situation where you have setTimeout and setImmediate interspersed and expect to see any type of guarantee about ordering there, you just won't have it.
Event Listeners Are Synchronous
I've got another challenge for you. Take a look at this code sample here. I'll save this so you have this available. Go ahead and download this Plunker if you'd like. Tell me, how many little programs do we have here, and, when I run this program, what will show in the console? Okay, let's go ahead and open up the console, clear this out. Are you ready? Let's run this program and see what happens. Did you expect that to happen? Interesting, right? We grab the button element, we add two listeners for the click event, print out click-1 and click-2 when that happens. It seems pretty reasonable that click-1 would come before click-2, but here's what's really interesting, we log out pre-click, we call click on the button, and then we log out post-click. There are two little programs here, not three or four. It might be tempting to think that there could be four little programs here, that these eventListeners would be separate programs that are fired off asynchronously, they're not. EventListeners are fired off synchronously. So what we have here, we log out pre-click, we call btn.click to simulate a click, which emits the click event, and that click event synchronously calls each of the handlers, that's why in between pre-click and post-click we have the click log messages. Once that's done firing off the event synchronously, then we can print out the last message here. So there are two little programs, the whole script, which wires up the event handlers and sets this timeout, and then there's a separate little program here that performs the logging and emits the event, which then synchronously calls the two listeners for that event. This seems kind of weird and you might be like, well how do I ever know? Well don't forget, debugging can be your friend. We can come in here and add a debugger statement. The debugger will open up here, and you can see we're right on this line for the first click event listener. We can add a breakpoint to the second listener, but we don't really need to do that because even with the first one we can take a look at the call stack, and we can see what we were interested in here. Take a look at this call stack, read through this and tell me if this does or doesn't confirm that the event listeners are fired off synchronously. Okay, so let's walk through this. At the very bottom of the call stack, and I have async call stacks turned on, we have script.js line 12, click on that here we can pull that up in the debugger, that's the setTimeout line. So that's the origination of this asynchronous call stack, and hence the setTimeout here with async in parenthesis. So the real call stack is this part right here, which is script.js line 14 emitting the button click event, and then you can see on top of that in the call stack is line 4, which is the debugger, which is where we're at right now. If I step over that into the next line, you'll see we're at line 5 here, we're still in that same call stack, so we have a synchronous call here. Line 14, call.click, and then pushed onto the stack is our listener. If I resume execution I'll hit the next breakpoint, and you can see here again, line 14 is at the very root of this call stack, and now line 9 is the current frame on the call stack, which is logging out click-2. Hop over to the console you'll see right now we have pre-click and click-1, if we resume the execution of our program, we now have click-2 and post-click. So event listeners fire off synchronously, keep that in mind. Don't confuse those as little programs, or asynchronous seams, they're not. If you wanted to perform something asynchronously as a result of an event, wrap it in a call to setTimeout inside of your event listener, so you could call setTimeout instead here, and then it would indeed be asynchronous. Now you might be wondering, why in the world would event listeners be synchronously called? Well it turns out, remember we can have race conditions with the order of operations in the queue, if something is asynchronous we don't have many constraints that we can rely upon to guarantee the order of execution, and thus some really bad things could happen. For example, here's a Node.js chunk of sample code that makes a web request. The response comes back as an event emitter, a source of events, the key I want to point out is if events aren't fired synchronously, and are fired asynchronously instead, then the data that comes back as a series of data events, there'd be no way to guarantee the order of that, and then a web page could get all jumbled up. This data event will fire multiple times, and if this weren't synchronous we couldn't guarantee that those asynchronous firings would then be handled in the correct order, and thus we wouldn't be able to know what the correct order is in our code. So event listeners have to be synchronous to ensure the order of events are preserved. Now this will sound really weird, but there can be asynchronicity between an event and another event of the same type being raised, but all the listeners for an event have to be fired off synchronously, so that we know they complete before we might be firing off another iteration of the same event.
Wes Higbee is passionate about helping companies achieve remarkable results with technology and software. He’s had extensive experience developing software and working with teams to improve how...
Released2 Jul 2016