Javascript Properly Allow Function to Complete Running Before It Can Be Run Again
Preamble
Let's acknowledge. JavaScript is non the most predictable linguistic communication out at that place. It might go pretty quirky very easily.
Let'southward expect at the following example.
setTimeout(() => console.log("1. timeout")); console.log("2. console"); Promise.resolve("iii. promise").then((res) => console.log(res)); // prints // 2. panel // 3. hope // ane. timeout Even if nosotros will alter the order of instructions, it won't impact the concluding issue ðĪĻ
Hope.resolve("1. promise").then((res) => panel.log(res)); setTimeout(() => console.log("2. timeout")); console.log("iii. console"); // prints // 3. console // 1. promise // 2. timeout It doesn't matter how nosotros will shuffle these 3 lines, they volition e'er end up executed in the same gild console, hope, timeout ð
Why? Well, yous know...
Of course, at that place is a expert (plenty) reason for that. And we'll go to it presently. Simply first, we need to analyze a affair or 2.
Put on your JavaScript hat and allow'south become! ðĐ
Nosotros are going to focus on the Web Browser JavaScript, all the same, well-nigh of the things we are going to discuss can be correlated to other agents, such as NodeJS.
Worth Mentioning:
setTimeout(() => {}) is equal to calling setTimeout(() => {}, 0).
Although neither will guaranty firsthand execution as the timeout value (0) is used to set up the minimum wait menstruum, not the exact period. Anyhow example to a higher place is completely legit in a given context.
1 Matter at a Time
At that place's one important attribute of JavaScript nosotros need to call out from the beginning. The unmarried-threaded nature of the environment it runs in.
It is difficult to enlarge the impact of this fact on the language, web browsers, and ultimately anything that runs JavaScript.
one thread === one phone call stack === i matter at a time
Suspension here for a sec... I thing at a time...
Even when it seems similar multiple things are happening simultaneously, in reality, there's just one unmarried chore that gets executed at every given moment, merely really fast.
The single thread nosotros were talking nearly is called browser main thread (nowadays more than accurate name would be a tab main thread ð)...
Thus everything that happens on the page is happening in one single thread. It is easy to underestimate the scale.
While our gorgeous code is running, meantime the web browser is rendering page content, receiving and dispatching all sorts of events, doing garbage collection, distributing time to come piece of work, and much more than...
What about JavaScript Panel, that affair we all use in the Browser Dev Tools? It depends, but nigh likely it will exist a different process, hence a different thread.
Exception...
The "single thread" matter is the default behavior, however, we tin branch from the main thread and run our JavaScript lawmaking in a separate thread with a help of Web Workers API.
A single thread is not a fault or a bad design.
Make JavaScript single-threaded was a witting decision... Years ago, the average computer had a single core and was less powerful than any mid-range phone today.
Websites were not really interactive (if at all), hence didn't really need whatever JavaScript magic. Who could foresee where it is going to cease up.
That Thing that Runs Your JavaScript
Ofttimes terms JavaScript Runtime and JavaScript Engine are used interchangeably.
Nevertheless, they are like table salt ð§ and light-green ðĐ. Ii completely different things. Allow me explain what I mean.
Three primary pieces found the JavaScript Runtime. They are conceptually separated. And most likely adult by different people/teams/companies, and stand for independent pieces of software. Even so, they work in close collaboration.
-
JavaScript Engine: compiles, optimizes, and executes code, handles retentivity allocation and garbage collection
-
Event Loop: orchestrates and distributes the piece of work, enables asynchronicity.
-
Browser Web API: allows advice with things located outside of the Runtime (e.g system timers, file system, HTTP, address bar, DOM, etc.)
The Large Motion-picture show
The Engine
The JavaScript Engine... does not run JavaScript...Information technology runs ECMAScript. Isn't it the same thing? Appears no, I'll explicate.
If we will expect through the source code of an arbitrary JavaScript engine (you know, cuz information technology is a coincidental affair we do lol ðĪŠ), nosotros volition find an implementation of the ECMAScript declaration. This will include all sorts of base objects (including Object) such as Date and String, primal language constructions like loops, conditions, and so along.
However, if nosotros will expect for say setTimer or fetch, we won't find much. Considering they are not part of ECMAScript. They are office of Browser Web API (nothing to do with Spider web itself really, more like Browser API ð, but you'll find it going under Web API, Spider web Browser API, Browser API, and simply API).
The JavaScript Engine volition be managing memory and controlling the execution of our fabled code. Which volition never exist executed in its original shape, the engine will keep modifying it all the time. Almost of the engines are pretty smart, they volition keep optimizing the lawmaking throughout the page lifetime in the constant hunt for performance improvements.
Important though is that the engine merely executes the lawmaking that it finds in the Stack of Frames (or Telephone call Stack or simply the Stack). Each frame represents a function call. While the engine is running the lawmaking, it might find a new role call (not to exist dislocated with function announcement) and button it to the Call Stack as a new frame.
Once a new frame has been added, the engine pauses the execution of the electric current frame and focuses on the new one.
After Engine finishes frame(function) execution it pops it from the stack and continues where it left, bold it is not the final frame. Every function call will cease upward every bit a new item on the Call Stack.
Worth mentioning that Engine does non own exclusive rights on pushes to the Call Stack, new work might be pushed from the outside of the engine boundaries (we'll talk almost it side by side). The Call Stack controls the execution sequence inside Engine.
Engine won't stop popping frames from the Call Stack until it is empty. And it won't allow any interruptions from exterior until it is done.
In the previous commodity Web Browser Beefcake we've already discussed some of the key JavaScript engine aspects (parsing, pre-parsing, compilation, and optimization/de-optimization). With a deeper focus on the V8 Compilation Pipeline. The article is more focused on the code processing itself and slightly touches Browser Engine (not to be confused with JavaScript Engine) and bones rendering concepts, so if it sounds interesting, don't forget to check information technology out later. ð
The Loop
The Event Loop is an orchestrator and the primary distributor of the work. Information technology does not perform the piece of work itself, but information technology ensures that the work is distributed in the expected manner (which may vary from browser to browser).
It is literally an infinite loop ♾️ which constantly keeps checking if in that location'due south any work it can schedule for execution. A simplified version would expect like this:
while (truthful) { if (allDone()) { const thingsToDo = getThingsToDo(); doThings(thingsToDo); } } On each iteration, the Event Loop performs an ordered series of jobs divers in the processing model documentation. Nosotros will be getting back to information technology through the form of the article.
The Result Loop and Upshot Loops
The Event Loop we usually refer to in the context of the web browser is a Window Event Loop. Every origin will become ane. Yet, sometimes few tabs/windows from the same origin might share a single loop. Specially when 1 tab is opened from some other. (This is where we can exploit multiple tabs/pages at in one case).
Anyway, Window Consequence Loop is not the only i issue loop running in the browser. Web workers (and other workers) will use its ain Worker Event Loop. Sometimes it will be shared across all workers. And worklets will have their own Worklet Event Loop.
But future when we refer to Event Loop we volition actually be referring to the Window Outcome Loop.
Tasks, Microtasks and Macrotasks
Given the unmarried-threaded nature of the language, it is hard to overstate the importance of asynchronicity. The async behavior is implemented by a set of queues (FIFO).
This is a very common arroyo. Queues are very comfortable for implementing asynchronicity in software (and across its boundaries). Think of a deject architecture. With a high probability in its middle, there will be some sort of queue that will be dispatching messages all over the place.
Anyhow, back to JavaScript:
There are two (not iii...) master types of queues, chore queue, and microtask queue. At the starting time glance, information technology might look similar they are identical. And information technology is true to some caste, they have the same office: postpone code execution for later. The difference lies in how Event Loop uses them.
You are probably wondering where did macrotasks go... Macrotask is just a V8 name for the job. So thereafter we volition use the term task and everything we say for the task tin be applied to macrotask.
Chore Queue
The task queue is what keeps the whole thing spinning. This is where most of our code gets scheduled for execution. Event the initial code (the one that nosotros place in-between the <script>...</script> tags) gets to the Call Stack through the Task Queue.
Oftentimes our code looks similar this:
practise this on button click do that when the server responds phone call the server In other words, nosotros define callbacks (what to do) and assign them to events (when to do) that suppose to trigger them. When the effect happens information technology does not execute the callback immediately, instead, it creates and enqueues a chore in the Task Queue, which in its turn will exist eventually processed (in other words pushed to the Call Stack).
The queue is out of our direct reach. Dequeueing is happening inside the consequence loop. Almost of the tasks are enqueued through so-called generic job sources. This includes user interactions, DOM manipulation, network activity, and history. Although we evidently take a way to touch on what and when volition get to the Job Queue (due east.g through result handling).
Ok, that'southward gonna be a tough sentence, so bear with me here... Dequeueing procedure happening once per iteration and information technology will least (continue dequeuing) until the newest job from the previous iteration (that accept been in the queue at the moment of the start iteration) is withal in the queue.
Proceed in mind that the newest tasks will be in the tail of the queue, due to FIFO (Starting time In Beginning Out) concept. In other words, all new tasks we are adding will exist executed in the next iteration, all current/sometime tasks will be executed in this iteration. As per processing model documentation.
ðŪ The chore queue is not really a queue, but an ordered set. Still, it is not very important as its behavior in this context is equivalent to the queue.
There might be (and probably volition exist) multiple task queues in a unmarried event loop. The nigh common reason for that is task priority management. E.one thousand. there might exist a split chore queue for user interactions and another queue for everything else. This way we can requite user interactions higher priority and handle them before annihilation else.
Microtask Queue
Promises, asynchronous functions all this goodness is empowered past the microtask queue. It is very similar to the chore queue, except for three major differences.
-
Microtasks are processed at different phases in the Consequence Loop iteration. We mentioned higher up that each Event Loop iteration post-obit strict order known equally the processing model;
-
Microtasks tin can schedule other microtasks and the new iteration of the Event Loop won't begin until we reach the terminate of the queue;
-
We can directly enqueue a microtask with queueMicrotask.
The balance is pretty much the same, once a task is dequeued and a callback is extracted, it will exist pushed to the Phone call Stack for immediate execution.
Browser Web API
The final piece in the puzzle is an API, the Browser API. The connectedness bridge between the code and everything exterior of the runtime.
Communication with a file system or remote service calls. Diverse effect subscriptions. Interactions with the accost bar and history. And more. Is facilitated by Browser API.
Browser API allows u.s. to define consequence handlers. And this is the well-nigh common way for developers to pass callbacks (event handlers) to the Task Queue.
Browser API are browser-specific. Each browser implements them separately. Hence they work differently, although probably will accept the same issue. Hence every now so yous might bump into a cool new feature that won't exist supported by
Browser X. And the most common reason, the API is not implemented in Browser X.
At to the lowest degree nowadays the naming is kinda conventional and no one tries to bear witness uniqueness... Imagine writing code when all browsers would proper name things differently and everything would produce different effects... That would exist a nightmare, wouldn't it?
Well, it used to be similar that. And it is kinda like this present. Fortunately, we have many tools similar BabelJS and a huge community behind them that helps mitigate this trouble for u.s..
I still remember ðī how y'all had to implement ajax calls (XMLHTTPRequest) for all possible browsers in your code until jQuery appeared. That was a game-changer.
Bringing Things Together
We've discussed quite a few things thus far. Permit's bring them all together in a single listing. And go over information technology in the same order every bit Event Loop will.
Remember that once some code gets in the Call Stack, the Engine will hijack command and start popping, executing, and pushing the lawmaking until finally, the Call Stack is empty. In one case reached the end of the stack it returns control to the same betoken where it hijacked it.
The browser volition find some JavaScript either in-between the <script> tags or in the DevTools Console. And ultimately it will button it to the Task Queue...
-
The Loop keeps checking the Task Queue. Once it finds the initial code the Loop volition move it to the Call Stack. The Engine immediately takes over and doing its task until it empties the Phone call Stack.
-
The Loop volition check microtask queue(south). It will keep dequeuing tasks from the queue and pushing them (one detail at a time) to the Call Stack (and information technology volition keep executing until empty) from the microtask queue until the microtask queue is empty. Remember that microtask lawmaking tin push another microtask in the queue and information technology will be executed during the aforementioned iteration (right here).
-
Both Engine Call Stack and Microtask Queue are now empty.
-
Finally, the Loop gets dorsum to the Task Queue. Proceed in mind that events were emitting all the time, either in the lawmaking or exterior of information technology. The Loop will marker the newest task (the one in the tail of the queue) in the queue and beginning dequeuing tasks from oldest to newest (caput to tail) and pushing code to the Engine Call Stack until it reaches marked task.
-
Next, it will practise some other unrelated to the runtime work, similar rendering.
-
Once all is done the new iteration starts from indicate one.
The Example
Permit'due south revisit the example from the beginning of the article:
setTimeout(() => console.log("i. timeout")); console.log("2. console"); Promise.resolve("3. hope").then((res) => console.log(res)); // prints // ii. console // three. promise // i. timeout Doesn't matter how nosotros would shuffle educational activity, the produced event will stay the aforementioned
Actually now it makes much more sense, cheque it out.
-
First, all this code is sent to the Phone call Stack and executed sequentially.
-
setTimeoutalmost immediately sends a callback to the Chore Queue. -
console.logprints string in the panel (this is our first line2. panel). -
Promise.resolve(...).then(...)is immediately resolved promise, thus it sends the callback to the Microtask Queue the same moment it is executed.
-
-
Stack finishes execution, it is empty and it passes control back to the Event Loop.
-
Result Loop checks Microtask Queue and finds there callback from the resolved promise and sends information technology to the Telephone call Stack (this is our second line
three. hope) -
Microtask Queue is empty, Call Stack is empty, information technology is Task Queue turn now.
-
The Issue Loop finds a timeout callback in the Task Queue and sends it to the Call Stack (this is our tertiary and last line
1. timeout).
And we are done, the stack is empty forth with all queues. That wasn't too bad, was it?
Recursion Examples
Alright, information technology is time to take some fun! ðĪ Given we already know how to interact and what to look from both queues and a stack. Nosotros will try to implement three unlike infinite recursion examples. Each volition utilize ane given mechanism.
Information technology will be more fun if y'all'd open a console and try to run code examples on your own. Just don't use this page's panel lol. I'd likewise advise preparing Browser Task Manager to go along an center on changes in retention and CPU consumption. Most of the modern browsers will have i somewhere in settings.
Let's first with classics.
Phone call Stack
const recursive = () => { console.log("stack"); recursive(); console.log("unreachable code"); }; recursive(); panel.log("unreachable code"); /* stack stack stack ... Uncaught RangeError: Maximum call stack size exceeded at recursive (<anonymous>:2:1) at recursive (<bearding>:three:1) at recursive (<anonymous>:iii:1) at recursive (<bearding>:3:ane) at recursive (<bearding>:3:1) at recursive (<anonymous>:iii:1) at recursive (<anonymous>:3:i) at recursive (<anonymous>:3:one) at recursive (<anonymous>:three:ane) at recursive (<bearding>:3:1) */ The infinite recursion and its expert erstwhile buddy Stack Overflow Exception. I bet you've seen a few of these earlier... The Stack Overflow Exception is nearly reaching the max size of the Telephone call Stack. Once nosotros exceed the max size it will blow up with a Maximum call stack size exceeded.
Annotation that there are a few console.log that will never get printed. Remember that every time we push button a new item on the Call Stack, the Engine volition immediately switch to information technology, since we are only pushing new items and never popping. The stack keeps growing until we reach its maximum...
Task Queue
Let's try the Task Queue now. This one won't blow up immediately, information technology will run much longer util the browser propose you impale the page (or wait if you lot are insistent).
const recursiveTask = () => { console.log("task queue"); setTimeout(recursiveTask); console.log("reachable code 1"); }; recursiveTask(); console.log("reachable code 2"); /* reachable code ii job queue reachable code 1 task queue reachable lawmaking 1 task queue reachable code 1 task queue reachable lawmaking 1 ... */ Note that both extra panel.log statements are printed. Considering all the fourth dimension nosotros are calculation a new chore to the Task Queue, we add it for the adjacent iteration and non for immediate execution.
Hence all lawmaking in this example is processed before starting a new iteration. Keep an eye on the memory footprint. It volition be growing adequately fast together with CPU usage. Under a infinitesimal my tab went over ane Gig of retentivity.
Microtask Queue
Ok, the final one, we'll do the same stuff, infinite recursion, only this time for the microtask queue.
const recursiveMicrotask = () => { console.log("microtask queue"); queueMicrotask(recursiveMicrotask); console.log("reachable code ane"); setTimeout(() => console.log("unreachable code 1")); }; recursiveMicrotask(); panel.log("reachable lawmaking 2"); setTimeout(() => panel.log("unreachable code two")); /* reachable code 2 microtask queue reachable lawmaking i microtask queue reachable code i microtask queue reachable code 1 microtask queue reachable code 1 ... */ Note how tasks from the Job Queue are never executed ("unreachable code"). This is happening because nosotros never stop upwards current Issue Loop iteration, we go on adding microtasks to the Microtask Queue and information technology prevents the iteration from finishing. If you will leave it for long enough you'll notice that the page (including the address bar) becomes less responsive.
Until it completely dies. Of course, the memory footprint (and CPU usage) will keep growing much faster, since we polluting the Task Queue, simply if nosotros will remove both setTimeout information technology will reduce the pace of memory footprint growth.
ð Side note
Recursion might be dangerous for infinity simulation. I'd recommend looking into generator functions for such matters. We won't become under the boot of generator functions. At least for now.
But hither'southward a pocket-sized instance of an infinite number generator, which shows the gist of information technology.
function* generateNumber() { let i = 0; while (true) yield i++; } const numbers = generateNumbers(); console.log(numbers.next().value); // 0 panel.log(numbers.next().value); // 1 console.log(numbers.next().value); // 2 That'due south it.
Of course, everything we looked at is a simplified representation. However, it illustrates in plenty detail how the Runtime functions.
It is accurate enough to explain the true nature of asynchronicity and code execution sequences in JavaScript. Too as hopefully reveal some "odd" behavior and "unexpected" race weather condition.
JavaScript has an extremely low entrance barrier. And frequently information technology is confused with beingness unstable. Withal, some of its behavior is a trade-off of some sort and payment for such a low entrance bulwark. Although few bugs are left there for backward compatibility lol...
If y'all enjoyed the read, don't forget to check out another related article Web Browser Beefcake.
Get-go published here.
Source: https://hackernoon.com/run-javascript-run
0 Response to "Javascript Properly Allow Function to Complete Running Before It Can Be Run Again"
Post a Comment