[00:00.000 --> 00:15.080] Hello, everyone. I am Bhavan. Yes, I'm here because I love software. Also, I really love [00:15.080 --> 00:22.560] talking. This is however the first time I'm giving a talk, so go easy on me. I work in [00:22.560 --> 00:29.840] Munich as a senior dev at this small startup called WorkerBase. I love DIY and I love [00:29.840 --> 00:36.480] making cocktails. That's me making one when I shouldn't be. I don't use a lot of social [00:36.480 --> 00:45.040] media, but you can find me on LinkedIn. Let's jump right into it and talk about some JavaScript [00:45.040 --> 00:54.760] architecture. I was secret to share with you. I've had the worst layover of my life at Berlin [00:54.760 --> 01:02.440] Airport and all I want to do right now is just sleep on a bench. You guys have to help me a [01:02.440 --> 01:08.280] little bit out here and make this more interactive so I don't go to sleep. Usually, it's the other [01:08.280 --> 01:13.240] way around. I had questions in there hoping that the audience will not go to sleep, but now it's on [01:13.240 --> 01:20.840] you guys. First thing, and this is a fairly easy and uncontroversial statement, I hope, [01:20.920 --> 01:27.880] right? The JavaScript engines are asynchronous. Is there anyone who disagrees with this? [01:30.040 --> 01:35.880] Do you all agree with it? Who agrees with it? Just raise your hand. [01:39.080 --> 01:44.440] It's fine. It's fine. Don't be ashamed. Just raise your hand. Okay. You agree with it? [01:44.520 --> 01:51.720] Three, four people who disagrees with it? Okay. Can one of you maybe tell me why you disagree? [01:52.440 --> 01:56.520] I mean, don't give a reason that because you're asking this question, obviously, [01:56.520 --> 02:00.600] the answer is no, but can you give a reason apart from that? [02:04.840 --> 02:10.760] Yes, exactly. So JavaScript engines are actually synchronous. [02:11.080 --> 02:19.800] Right? JavaScript and time environments are, in fact, asynchronous, right? And we'll talk about [02:19.800 --> 02:26.280] in this talk what's the difference between the two, right? But so far, does anyone get what I'm [02:26.280 --> 02:29.880] saying? There's the engine and then there's the runtime environment and there are two separate [02:29.880 --> 02:41.480] things, right? Final question. Is Node.js single-threaded? Yes. Anyone says no? [02:43.880 --> 02:56.840] Okay. One guy. Two. Good. I'll not bother you too much with this. There's a bit of a bad question, [02:56.840 --> 03:01.960] so to say, because I should have defined what do I mean by Node.js here? Do I mean the runtime or [03:01.960 --> 03:07.080] do I mean the whole ecosystem? But colloquially, when you say Node.js, you mean the whole thing, [03:07.080 --> 03:14.440] right? And that is not always single-threaded. There are parts of it that are actually multi-threaded [03:15.000 --> 03:23.720] and we will try and demystify some of these things, right? So this is what the JavaScript [03:24.520 --> 03:30.920] runtime environment, wow, that's a mouthful. This is what it looks like, right? Up there, [03:30.920 --> 03:37.320] you have your V8 engine, which is the JavaScript engine. I mean, it doesn't have to be V8. It's [03:37.320 --> 03:46.280] V8 for Chrome, SpiderMonkey, for Mozilla, so on and so forth. But that's the JavaScript engine, [03:46.280 --> 03:52.600] right? That's the thing that understands JavaScript and parses it and does a bunch of things, right? [03:52.600 --> 04:00.600] It reads and reads JavaScript, does stuff. And that thing, as we just mentioned, is synchronous. [04:01.400 --> 04:09.000] There's a few other things here that will actually give you the asynchronous part, right? [04:10.520 --> 04:16.840] But let's talk first only about V8, right? So what does V8? And when I say V8, I'm only using it as [04:16.840 --> 04:20.840] a placeholder for JavaScript engine, right? It could be any engine. It doesn't matter [04:20.840 --> 04:27.160] for the purpose of this talk. So what does it do? It does memory allocation. You have your heap, [04:27.160 --> 04:33.320] so it manages, it randomly allocates memory whenever it needs to store like a variable or something. [04:34.680 --> 04:41.960] It has the execution context, which is a fancy term for your call stack, right? And we'll talk [04:41.960 --> 04:52.040] about what call stack is in a slide or two. It is also single threaded, right? And synchronous. [04:56.520 --> 05:04.920] Okay. So yeah, I pretty much covered that, I guess. A quick intro to call stack. If you have ever seen [05:05.000 --> 05:11.800] an error message in JavaScript, what you see there is your call stack, right? It's a snapshot of [05:11.800 --> 05:19.160] your call stack when that error happens. That's basically what it is. And it's single threaded, [05:19.160 --> 05:25.080] which means that there is only one call stack in the JavaScript engine, right? So in other words, [05:25.080 --> 05:29.560] it can only do one thing at a time, right? If it has to do two things, [05:31.160 --> 05:34.360] it cannot. It has to first finish what it's doing and then do the next thing. [05:35.480 --> 05:43.320] Right? Let's look at a quick example for this, right? So what I have here is a simple pseudo [05:43.320 --> 05:50.840] code. Well, not pseudo code. It's a working code, right? So we have three functions here. [05:51.960 --> 05:55.400] It's pretty self-explanatory, right? I don't need to explain you what's going on here. [05:57.480 --> 06:04.280] What we will see on this side is what's happening with the call stack, right? So as the execution [06:04.280 --> 06:12.680] starts, you have your first function. Okay. First of all, you have sort of the general execution [06:12.680 --> 06:19.240] context, right? Which is sort of like the main equivalent of your JavaScript engine. Like if [06:19.240 --> 06:23.800] you've ever done like cc++ code, there's this main function, right? So that's this. [06:26.360 --> 06:32.920] You have a bunch of functions, nothing to do so far, nothing to execute. And then we actually [06:32.920 --> 06:40.440] come to a statement, right? And what do we want? We want to print greeting. So that adds something [06:40.440 --> 06:46.520] to the call stack, right? So now we are going to this function, print greeting. What do we do [06:46.520 --> 06:54.360] inside of print greeting? We call the greet function, right? And we call it with some value, [06:54.360 --> 07:00.200] but that's not important. So when we call the greet function, one more thing gets added to the call [07:00.200 --> 07:05.720] stack. And what are we doing in the greet function? We are calling the join words function, [07:06.520 --> 07:11.640] right? One more thing gets added to the call stack. And now you hit return, right? So now we [07:11.640 --> 07:20.040] actually have to return something. So something gets popped from the stack, right? So now you are [07:20.040 --> 07:28.840] out of the join function. You now, you go to the return statement of greet, you are out of greet. [07:30.200 --> 07:35.720] You go into the next statement of your print greeting. You do your console log. [07:37.480 --> 07:42.040] And there's no return statement here, but it's end of the function. So you're going out of this [07:42.040 --> 07:49.960] one as well, right? And you're back to your main thread. And that's it. That's how asynchronous, [07:51.320 --> 07:59.560] not asynchronous. That's how asynchronous JavaScript code runs, right? So far so good. [08:00.200 --> 08:09.240] Everyone with me? Great. Another example, like I was saying, if you've ever seen an error stack, [08:10.680 --> 08:17.000] essentially what you see is the call stack, right? You see a snapshot of the stack when the error [08:17.000 --> 08:23.720] happened, right? Or if you ever use the debug tool, for example, you are also seeing the call stack [08:23.720 --> 08:33.160] over there. Now let's look at something slightly different. What happens in this case, right? [08:35.080 --> 08:39.800] We all know what would happen without referring to the stack, right? It'll give hello, [08:39.800 --> 08:46.440] it'll give there, and then it'll give forcedM, right? But now there's sort of two things happening here. [08:47.720 --> 08:52.920] There's a mistake there. I forgot to add the time. It'll be there in the next slide. Just ignore that. [08:54.120 --> 09:01.400] But yeah. So what happens, right? Because our call stack now cannot do two things at a time, [09:01.400 --> 09:08.040] right? And we know from experience that setTimeout is going to sort of run [09:08.040 --> 09:17.080] parallelly while the stack moves on to the next thing, right? So that's where the other things [09:17.080 --> 09:24.360] that we had in that previous picture come into play, right? So you have mainly three things here. [09:24.360 --> 09:32.360] You have your web APIs. In the browser, you'll have web APIs. If you're doing Node.js, you'll have [09:32.360 --> 09:38.760] what's called libUV. And towards the end, I'll also talk a bit about libUV. But for now, let's [09:38.760 --> 09:44.760] assume we are in the browser, right? So we are on the client side. You have these web APIs here. [09:45.080 --> 09:53.720] You have your task queue. And you have the star of the stock, the rotating thing, what's called the event loop, [09:53.720 --> 10:04.040] right? And you will go through in more detail. But just to summarize, all the stuff that is sort [10:04.040 --> 10:10.680] of slower, right? That's not happening immediately. That's not being invoked immediately or running [10:11.320 --> 10:18.440] synchronously. That gets delegated to the web APIs, right? So here you have your DOM manipulation. [10:19.080 --> 10:27.160] You can make XHR calls. You can do setTimeout. If you were in Node.js, you could make a call [10:27.160 --> 10:36.600] to the database. Anything that's slow runs here, right? But the whole point of doing this is that [10:36.600 --> 10:41.880] after you run something, you want to do something back into your main thread, right? [10:41.880 --> 10:47.800] You are finally, you're running JavaScript. And you are making a call to some external system [10:47.800 --> 10:53.880] web or doing some delay. But finally, you then what, right? You need to do something. And that [10:53.880 --> 11:01.720] something is handled by the task queue, right? So take a simple example of a setTimeout. [11:02.440 --> 11:11.880] You have a callback and some delay, right? So the actual waiting for the time, say you put a delay [11:11.880 --> 11:17.320] of 300 milliseconds. So the actual waiting for 300 milliseconds is done by the web API. [11:18.280 --> 11:26.680] Then your callback goes to the task queue. And then it has to somehow go back to your main [11:26.680 --> 11:30.680] call stack and get executed, right? Because your callback is still JavaScript. [11:30.680 --> 11:35.320] I mean, we are assuming it's a simple callback here. Obviously, that itself could have another [11:35.320 --> 11:39.000] callback and then the process repeats, right? But let's assume for now we are just doing like a [11:39.000 --> 11:43.160] console or you know, callback, right? So that's pure JavaScript and it has to go back to the stack. [11:44.440 --> 11:55.160] And the event loop is what decides this part, right? So the event loop checks if the stack is [11:55.160 --> 12:03.560] empty, if the call stack is empty, as in if it's idle, then check the task queue. If there's [12:03.560 --> 12:10.760] something in the task queue, move it to the call stack, right? This is in a nutshell what's happening. [12:11.880 --> 12:17.240] This is in a nutshell how JavaScript manages to have asynchronous features, right? While [12:17.240 --> 12:19.640] still itself being single threaded and synchronous. [12:22.760 --> 12:32.680] So let's look at our function. And now we have, let's run this, right? So we have our main. [12:32.680 --> 12:52.760] We go to console log and we log hello. Okay. I don't know. We'll just, we'll not do full screen. [12:53.640 --> 13:04.360] Ah, I know what's not working. So that there's, there's a gif here of, of like a timer. So just, [13:04.360 --> 13:12.840] just assume that for now, right? But, but yeah, it's not important. We can still manage without [13:12.840 --> 13:20.680] it, right? So let's, let's go back. Let's start from the top, from the bottom, right? So you have [13:20.680 --> 13:30.120] your main, you have your main function, then you call your console log that gets executed. So it gets [13:30.120 --> 13:35.320] popped off the stack. You guys know this, but I'm doing this practice because I'm also going to [13:35.320 --> 13:40.760] ask you questions about this, right? So it's a nice thing to visualize for some examples, right? [13:41.960 --> 13:48.440] You have your set timeout, right? Set timeout is not on the stack. It gets delegated to the [13:48.440 --> 13:54.360] web API, right? Which starts a timer. And then it knows that it has to run a callback when the [13:54.360 --> 14:01.320] timer is finished, right? While this is running, we move on to the next thing that we can do, right? [14:01.320 --> 14:08.760] In the stack. And then we have another console log that gets executed. It goes away as in it's [14:08.760 --> 14:16.360] popped. And then the main thread is free, right? And then after some time, so, so you see that [14:16.360 --> 14:21.560] tick? There was supposed to be a loading thing there, right? To show that it's still counting. [14:22.920 --> 14:27.960] After some time, your two seconds are done. And then, [14:35.000 --> 14:39.720] so after some time, your two seconds are done. Your timer is over. That's also done. [14:39.800 --> 14:48.920] You move the callback to your task queue, right? And then the event loop checks if the call stack [14:48.920 --> 14:57.400] is free, which in this case it is free, right? It moves the callback function over to the stack [14:58.440 --> 15:05.560] and then it runs it, right? So there's again a console log that gets printed and you're done, [15:06.280 --> 15:15.880] right? So to summarize the JavaScript engine itself is synchronous and single-threaded. [15:17.880 --> 15:25.800] The Node.js runtime is asynchronous because it manages the asynchronous things outside of [15:25.800 --> 15:32.120] the JavaScript engine, right? In a separate thing, right? Which is usually written in C++, [15:32.120 --> 15:42.120] either by VPIs or LibUV. And the event loop is the glue that ties all of this together, right? So [15:42.120 --> 15:48.360] wherever your thing is running, the callback goes to the task queue and then the event loop [15:48.360 --> 15:59.080] decides when to run these callbacks. All right. Before I go there, does anyone have a question [16:00.040 --> 16:01.560] so far? Yeah? [16:03.880 --> 16:10.600] During that event loop, are they green threads that are created? How does that work from [16:11.880 --> 16:17.480] lower level? Sorry, can you repeat it? Are they green threads that are created then? [16:18.200 --> 16:26.040] How in terms of lower level, does it become multi-threaded at that moment? [16:28.600 --> 16:36.520] Yeah? You want me to give them the mic? Okay, yeah. So the question is, in the event loop, [16:38.040 --> 16:44.840] are there green threads that are created? The event loop does just one thing, right? It's a loop. [16:44.840 --> 16:55.000] It's basically a while too, right? And we will try and make a more complex model of it. But for [16:55.000 --> 17:01.720] now, for our understanding, it's doing just one thing. Go to the queue. If there's something in [17:01.720 --> 17:08.360] the queue and nothing in the stack, pull the first thing from the queue and put it in the stack, [17:08.360 --> 17:12.360] right? That's all it's doing. So there's no multi-threading. There's no nothing. It's just [17:12.360 --> 17:21.960] a while loop. There is multi-threading in the other part over here, but in Node.js, right? Not in [17:21.960 --> 17:36.600] the browser. All right. So, exercise time, right? This is a super simple function. Ignore all the [17:36.680 --> 17:45.080] boilerplate in the HTML. I was too lazy to remove it. All that that HTML is doing is it has a script.js [17:45.080 --> 17:53.080] and the script.js is over here, right? What is the script.js doing? Pretty simple to, pretty [17:53.080 --> 17:58.040] similar to what you guys saw earlier. It has two console logs, a set timeout in between, [17:58.840 --> 18:05.560] and another console log in the callback, right? So the first question is this, what's the output? [18:07.080 --> 18:16.120] Perfect, right? That's very simple. We just saw it. Second question, what will be the max [18:16.120 --> 18:25.160] number of tasks in the task queue? Right? This is a little bit tricky. One. [18:25.880 --> 18:31.080] Two. Who said two? Why? [18:38.440 --> 18:40.680] Yeah, but console log is just going to run on the stack. [18:42.280 --> 18:48.840] So, see, your answer is right, but the reason is wrong, right? So there will in fact be two things, [18:48.840 --> 18:59.000] right? Because reading the script tag itself is a task, right? In this case, the script tag [18:59.000 --> 19:03.720] is a different file, so it actually has to, I don't know how browsers internally do it, [19:03.720 --> 19:08.360] but actually has to go to the location and read the content. But even if the script tag is on [19:08.360 --> 19:17.720] the HTML, it's still a different task, right? And this is important to understand if you have to [19:17.720 --> 19:22.680] guess, even there's a race condition basically, right? And when we come to micro tasks, [19:22.680 --> 19:27.320] this will become a little bit more complex, and we have an example for that. But just [19:27.320 --> 19:37.480] keep in mind for now that the script tag is also a task, right? And that's the last part [19:38.280 --> 19:46.360] in the learning. So this talk, I don't know if anyone read the description, is in three parts, [19:46.360 --> 19:52.040] right? So there's intro to what happens in the browser, then there's a deep dive of [19:52.920 --> 19:57.880] the loop itself, or, well, the task queue, and then the third part is Node.js. And we are done [19:57.880 --> 20:04.680] with the first part, right? So everything looks good so far, right? It's a little bit janky, [20:04.680 --> 20:08.440] they did some weird things there, but we get what's happening now, right? [20:09.400 --> 20:18.760] Well, not exactly. I mean, we do, but there's more nuances, right? So let's do a deep dive [20:18.760 --> 20:25.720] into what's actually happening inside of the loop and how our different tasks handle, right? [20:25.720 --> 20:33.000] So as the line says, not all tasks are created equal. This is the model we had so far, right? [20:33.000 --> 20:39.720] That the task queue is a simple single queue, which had callback 1, callback 2, callback 3, [20:39.720 --> 20:47.000] whatever. And every time the event loop is a while loop, right? So in each cycle of the [20:47.000 --> 20:51.640] while loop, which is, by the way, called a tick, right? That's just the term. You might have heard [20:51.640 --> 20:59.080] this sometimes next tick, right? That's what it's talking about. Okay. I've been told I don't have [20:59.080 --> 21:07.240] a lot of time. Let's try to fit this because we are, like, barely halfway there. In reality, [21:08.040 --> 21:14.520] there are multiple queues inside of your task queue, right? So what I told you earlier was a bit of a [21:14.520 --> 21:22.920] lie. And actually JavaScript or, well, the JavaScript ecosystem does not handle each task [21:22.920 --> 21:29.400] equally, right? Some are given a higher priority than the others. Click events for example. And [21:29.400 --> 21:35.000] this varies a bit from browser to browser. It's different in Node.js. So don't take this as like [21:35.000 --> 21:39.960] Bible. This is just an example to show you, right? And it's also oversimplified, obviously. [21:41.480 --> 21:47.160] But click events are given a higher priority and then everything else, right? [21:47.720 --> 21:58.440] There is also something called a request animation frame called back queue, right? And what the F is [21:58.440 --> 22:09.400] that, right? So the browser, sorry, JavaScript and time is also responsible for rendering, right? [22:09.400 --> 22:14.040] It's also responsible for drawing things on the screen because the browser is doing that, right? [22:14.040 --> 22:21.240] And it doesn't do it on every tick, right? Because that would be wasteful, right? Because [22:21.240 --> 22:26.920] you have a tick happens roughly, let's say, one millisecond. Not really, but let's assume that. [22:26.920 --> 22:33.960] Whereas if you have a 60 hertz screen, you only need to refresh every 16 or 17 milliseconds, right? [22:33.960 --> 22:38.840] So it's smart enough to understand that I don't need to do this all the time, but it does need to [22:38.840 --> 22:47.880] do it at some point, right? And if you block the event loop, you're going to freeze your screen, [22:47.880 --> 22:55.000] right? That's the big take home message. So let's look super quickly at an example. [22:55.000 --> 23:03.480] We have three frames here, right? And there's a rendering step happening on each frame, right? [23:03.480 --> 23:12.120] And now what we want to do is we want to... So the time is up, but if you guys don't mind, [23:12.120 --> 23:18.040] I'm maybe going to take five more minutes. If you have questions, maybe hit me up later, [23:18.040 --> 23:24.280] but I at least want to finish this part, right? So you have the rendering step. [23:26.600 --> 23:33.080] And now say if you want to change some logic in the rendering step or related to rendering. [23:33.640 --> 23:39.480] You would just do like a timeout or something, right? Say, run this logic every, I don't know, [23:39.480 --> 23:46.840] at 60 hertz frequency. But that's not very good because as we saw in the set timeout zero example, [23:47.480 --> 23:53.400] the time you give in set timeout is not guaranteed, right? It's the minimum time that it'll wait. [23:53.400 --> 23:58.600] If your queue, if your stack is not empty, when your time is up, then it'll wait for the next [23:58.600 --> 24:02.200] take, it'll wait for the stack to be empty and only then will it do something, right? [24:02.920 --> 24:09.880] So if you just ran this as is in like a set timeout, that'd be a very bad user experience, [24:09.880 --> 24:14.680] right? Because you might skip a few frames, you might have visual artifacts, and all kinds [24:14.680 --> 24:19.880] of weird things can happen, right? So that's why we have a separate queue for this, right? [24:20.680 --> 24:24.040] And that queue can be accessed through what's called request animation frame. [24:24.760 --> 24:36.600] And that's this in the parlance of my coloring, right? So let's quickly summarize. The event [24:36.600 --> 24:44.840] loop is responsible for rendering frames, but not in every cycle. But when it does render something, [24:44.840 --> 24:52.840] it takes everything in the request animation frame queue and renders it altogether, right? [24:53.480 --> 24:57.960] So you have this, and then you have micro tasks. [25:00.360 --> 25:08.200] And micro tasks are basically promises, right? They're not really, but we are oversimplifying, [25:08.200 --> 25:11.880] and we are anyways out of time. So let's just stick with that for now, right? [25:12.760 --> 25:20.760] The big difference with micro tasks is that the queue has to be empty whenever it's run, right? [25:20.760 --> 25:25.240] Even if the micro task creates another task that also needs to get executed, [25:25.240 --> 25:32.200] you don't wait for the next time, right? So let's look at an example super quick. So [25:32.200 --> 25:36.840] in one tick, in one cycle, right, on which animation is also going to get rendered, [25:37.720 --> 25:45.720] you will pick up one of the regular events. You would do all the micro tasks, right? [25:45.720 --> 25:54.200] If you have more micro tasks, you will do all of them. Then you will go to the animation frame. [25:54.840 --> 26:00.520] If you have more tasks for animation, you will not do them, right? You will wait for the next [26:00.520 --> 26:06.440] animation cycle to do it. That's some big difference between these two. And again, [26:06.440 --> 26:12.440] as you can see, if you mess up micro tasks, you can end up freezing up your screen, right? [26:12.520 --> 26:17.400] You put a while loop. You do a task that calls itself, like a promise that calls itself, [26:17.400 --> 26:20.520] and then everything is going to get frozen. That's because of this. [26:23.000 --> 26:31.960] All right. I think we are out of time. This is maybe the one last thing I want to do, [26:31.960 --> 26:38.280] if you guys don't mind, and then we'll stop, right? So can someone tell me what would be [26:38.280 --> 26:42.520] the answer to this? Awesome. Can you explain it? [27:00.520 --> 27:08.040] Perfect. Yes. And also keep in mind that the script itself is a task, right? Because why [27:08.040 --> 27:17.560] not be otherwise, right? So why not just put the set time out in the queue and execute it? [27:17.560 --> 27:23.960] But you can't because script itself is in the queue, right? So the set time out will go afterwards. [27:25.400 --> 27:36.280] All right. That's about it. Thanks a lot. Hope you guys learned something. [27:38.040 --> 27:40.280] Thank you.