Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to understand Asynchronous Ibank O and event Loop in Nodejs

2025-03-29 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

In this issue, the editor will bring you about how to understand asynchronous Imax O and event cycle in Nodejs. The article is rich in content and analyzes and narrates it from a professional point of view. I hope you can get something after reading this article.

The concept of Asynchronous Ipaw O

The processor can access any data resources other than registers and Cache, including external devices such as memory, disk, video card, etc. Operations in Nodejs, such as developers calling fs to read local files or network requests, are all Icano operations. (the most common abstractions are file operations and TCP/UDP network operations.)

Nodejs is single-threaded, and in single-threaded mode, tasks are executed sequentially, but if the previous tasks take too long, it is bound to affect the progress of subsequent tasks. Usually, the calculation between INodejs O and cpu can be carried out in parallel, but in synchronous mode, I O will lead to the waiting of subsequent tasks, which hinders the execution of tasks and results in poor utilization of resources.

In order to solve the above problem, Nodejs chose the mode of asynchronous Ihop O, so that the single thread is no longer blocked and uses resources more reasonably.

How to treat Asynchronous Ibank O in Nodejs reasonably

Front-end developers may have a clearer understanding of the asynchronous tasks of JS in the browser environment, such as initiating an ajax request, just as ajax is an api that can be called in the browser's js execution environment, and the http module is provided in Nodejs to allow js to do the same thing. For example, listen | send http requests. In addition to http, nodejs also has a fs file system that operates local files.

These tasks, such as fs http above, are called Icano tasks in nodejs. After understanding the I _ paw O task, let's analyze the two forms of the I _ map O task in Nodejs-blocking and non-blocking.

Synchronous and Asynchronous IO modes in nodejs

Nodejs provides both blocking and non-blocking uses for most Istroke O operations. Blocking means that you have to wait for the result to execute the js code when you perform the Ibank O operation. Here is the blocking code

Synchronous Ipaw O mode

/ * TODO: blocking * / const fs = require ('fs'); const data = fs.readFileSync ('. / file.js'); console.log (data)

Code blocking: read the file.js file in the same directory, the result data is buffer structure, so when reading process, will block the execution of the code, so console.log (data) will be blocked, only when the result is returned, data can be printed normally.

Exception handling: a fatal point of the above operation is that if an exception occurs (for example, there is no file.js file in the same directory), it will cause the whole program to report an error, and the following code will not be executed. Try catch is usually required to capture error boundaries. The code is as follows:

/ * TODO: block-catch exception * / try {const fs = require ('fs'); const data = fs.readFileSync ('. / file1.js'); console.log (data)} catch (e) {console.log ('error:', e)} console.log ('normal execution')

Even if an error occurs above, it will not affect the execution of subsequent code and the exit caused by an error in the application.

The synchronous IBG O mode causes the code execution to wait for the IZP O result, wasting the waiting time, the processing power of the CPU is not fully utilized, and the failure of IBO will make the whole thread exit. The diagram of blocking I / O on the entire call stack is as follows:

Asynchronous IPUBO mode

This is the asynchronous Ibank O just introduced. First, take a look at the Istroke O operation in asynchronous mode:

/ * TODO: non-blocking-Asynchronous fs.readFile O * / const fs = require ('fs') fs.readFile ('. / file.js', (err,data) = > {console.log (err,data) / / null}) console.log (111) / / 111is printed first ~ fs.readFile ('. / file1.js', (err,data) = > {console.log (err,data) / / save [no such file or directory, open'. / file1.js'] The file was not found. })

The callback callback is executed asynchronously, and the first parameter returned is the error message, if there is no error, then null is returned, and the second parameter is the real content of the fs.readFile execution.

This asynchronous form can gracefully catch errors in the execution of file1.js O, for example, when reading a file1.js file, the abnormal behavior that the corresponding file cannot be found will be passed directly to the callback in the form of the first parameter.

For example, callback, as an asynchronous callback function, like fn of setTimeout (fn), does not block code execution. It will be triggered after the result is obtained, and the details of the Nodejs asynchronously executing the Imax O callback will be analyzed later.

For the processing of asynchronous Icano, Nodejs internally uses a thread pool to handle asynchronous Icano tasks, and there will be multiple Imax O threads in the thread pool to handle asynchronous Imax O operations at the same time, such as in the example above. This is the case in the entire IWeiO model.

Next, let's explore the asynchronous Ihop O execution process.

Event cycle

Like browsers, Nodejs also has its own execution model-event cycle (eventLoop). The execution model of event loop is affected by the host environment, and it is not part of the javascript execution engine (such as v8), which leads to different event loop patterns and mechanisms in different host environments. The intuitive embodiment is that there are differences in the processing of micro tasks (microtask) and macro tasks (macrotask) in Nodejs and browser environments. The event loop of Nodejs and its each phase will be discussed in more detail next.

The event cycle of Nodejs has several stages, one of which is dedicated to dealing with the callback of Icano. Each execution phase can be called Tick. Each Tick will query whether there are any other events and the associated callback function. For example, the callback function of asynchronous Icantho will check whether the current IUnip O is completed during the IUnip O processing phase. If it is completed, then execute the corresponding callback function. So the observer who checks whether the Imax O is complete or not is called the Imax O observer.

Observer

As mentioned above, the concept of "Nodejs O observer" is also mentioned. It is also mentioned that there will be multiple stages in the Tick. In fact, each stage has one or more corresponding observers. Their job is clearly to find out whether there is a corresponding event execution in each corresponding Tick process, and if so, take it out and execute it.

Browser events come from user interaction and some web requests such as ajax, etc. In Nodejs, events come from web request http, file Imax O, and so on. These events all have corresponding observers. I enumerate some important observers here.

File Icano operation-- Igamo viewer

Network Icano Operation-- Network Icano Observer

Process.nextTick-idle Observer

SetImmediate-check Observer

SetTimeout/setInterval-delayer observer

...

In Nodejs, the corresponding observer receives the corresponding type of event. During the event loop, the observer is asked if there is a task to be performed. If so, the observer will take out the task and give it to the event loop to execute.

Request object and thread pool

The request object plays a very important role from the JavaScript call to the time when the computer system executes the callback, so let's take an asynchronous Imax O operation as an example.

Request object: for example, a previous call to fs.readFile essentially calls a method on libuv to create a request object. This request object retains the information of this Ipicuro request, including the body and callback function of this Ipicuro. Then the first phase of the asynchronous invocation is completed, JavaScript continues to execute the code logic on the stack, and the current IWeiO operation is put into the thread pool in the form of a request object, waiting to be executed. It achieves the goal of asynchronous IBO.

Thread pool: the thread pool of Nodejs is provided by the kernel (IOCP) under Windows, which is implemented by libuv itself in Unix system. Thread pool is used to perform part of the Icano (operation of system files). The thread pool size defaults to 4. Requests for multiple file system operations may be blocked into one thread. So how does the Istroke O operation in the thread pool be performed? As mentioned in the previous step, an asynchronous Ithumb O will put the request object in the thread pool. It will first determine whether there are available threads in the current thread pool. If the thread is available, it will execute the Icano operation of the request object and return the result of the execution to the request object. In the Iamp O processing phase of the event loop, the Ipicot O observer gets the completed Ipicot O object, and then fetches the callback function and the result call for execution. The Ipaw O callback function is executed in this way, and the result is regained in the callback function's parameters.

Asynchronous Icano operation mechanism

The above describes the entire execution process of asynchronous Iripple O, from the trigger of an asynchronous Ithumb O to the callback to execution of the Asynchronous Imax O. Event loops, observers, request objects, and thread pools constitute the entire asynchronous Iripo execution model.

Use a picture to show the relationship between the four:

Summarize the above process:

The first stage: for each call of asynchronous Igamot O, the request parameters and callback function callback are set at the bottom of nodejs to form the request object.

The second phase: the formed request object will be put into the thread pool, and if the thread pool has a free Iwhite O thread, it will execute the IUnip O task and get the result.

The third stage: in the event loop, the Iamp O observer will find the result of the Icano request object from the request object, take out the result and the callback function, put the callback function into the event loop, execute the callback, and complete the entire asynchronous Iampo task.

How do you perceive that the asynchronous Ithumb O task has been executed? And how do you get the completed task? As the middle layer, libuv uses different means on different platforms. It is implemented through epoll polling under unix, IOCP under Windows, and kqueue under FreeBSD.

Event cycle

The event loop mechanism is implemented by the host environment

It has been mentioned above that the event loop is not part of the JavaScript engine, and the event loop mechanism is implemented by the host environment, so the event loop is different in different host environments. Different host environments refer to browser environment or nodejs environment, but in different operating systems, the host environment of nodejs is also different. Next, use a diagram to describe the relationship between the event loop in Nodejs and the javascript engine.

Take the event loop of nodejs under libuv as reference, and the relationship is as follows:

Take the event loop of javaScript under the browser as a reference, and the relationship is as follows:

The event loop is essentially like a while loop, and as shown below, I'll use a piece of code to simulate the execution of the event loop.

Const queue = [...] / / queue contains the pending event while (true) {/ / start the loop / / execute the task in queue / /. If (queue.length = 0) {return / / exit process}}

After Nodejs starts, it is like creating a while loop, in which there are events to be processed. In each loop, if there are any events, the event is taken out and the event is executed. If there is a callback function associated with the event, the callback function is executed and the next loop is started.

If there are no events in the body of the loop, the process will exit.

I summarized the flow chart as follows:

So how does the event loop handle these tasks? We list some common event tasks in Nodejs:

SetTimeout or setInterval delay timer.

Asynchronous Ihop O tasks: file tasks, network requests, etc.

SetImmediate task.

Process.nextTick task.

Promise micro task.

I'll talk about how these tasks work and how nodejs handles them one by one.

1 event cycle phase

For different event tasks, they are executed at different event cycle stages. According to the nodejs official documentation, in general, the event loop in nodejs may have special phases depending on the operating system, but overall it can be divided into the following six phases (six phases of the code block):

/ * ┌──┐┌─ > │ timers │-> timer Execution of the delay timer │ └─┬─┘│ ┌─┴─┐│ │ pending callbacks │-> iUnip o │ └─┬─┘│ ┌─┴─┐│ │ idle Prepare ││ └─┬─┘ ┌─┐│ ┌─┴─┐ │ incoming: ││ │ poll │ stop_flag = 0) {/ * time of update event loop * / uv__update_time (loop) / * Phase I: timer Phase execution * / uv__run_timers (loop); / * Phase II: pending Phase * / ran_pending = uv__run_pending (loop); / * Phase III: idle prepare Phase * / uv__run_idle (loop); uv__run_prepare (loop); timeout = 0 If ((mode = = UV_RUN_ONCE & &! ran_pending) | | mode = = UV_RUN_DEFAULT) / * calculate timeout time * / timeout = uv_backend_timeout (loop); / * fourth stage: poll phase * / uv__io_poll (loop, timeout); / * fifth stage: check phase * / uv__run_check (loop) / * Phase 6: close stage * / uv__run_closing_handles (loop); / * determine that there are still tasks in the current thread * / r = uv__loop_alive (loop); / * omit the flow after * /} return r;}

We can see that the six stages are carried out in sequence, and only by completing the tasks of the previous stage can we proceed to the next stage.

When uv__loop_alive determines that there is no task in the current event loop, it exits the thread.

2 task queue

During the entire event loop, four queues (the actual data structure is not a queue) are performed in the event loop of libuv, and two queues are executed in nodejs, respectively, the promise queue and the nextTick queue.

There is more than one queue in NodeJS, and different types of events are queued in their own queues. After processing one phase, and before moving to the next phase, the event loop processes two intermediate queues until the two intermediate queues are empty.

Libuv processing task queue

At each stage of the event loop, the contents of the corresponding task queue are executed.

Timer queue (PriorityQueue): the data structure is essentially a binary minimum heap, and the root node of the binary minimum heap gets the callback function corresponding to the timer on the nearest timeline.

Igamo event queue: stores the Ipicuro task.

Immediate queue (ImmediateList): multiple Immediate, and the node layer is stored in a linked list data structure.

Close the callback event queue: place the callback function to be close.

Non-libuv intermediate queue

NextTick queue: stores the callback function of nextTick. This is unique in nodejs.

Microtasks micro-queue Promise: stores the callback function of promise.

Execution characteristics of intermediate queues:

First of all, it is important to understand that the two intermediate queues are not executed in libuv, they are executed in the nodejs layer, and after the libuv layer processes each stage of the task, it communicates with the node layer, then the tasks in the two queues are given priority.

The priority of the nextTick task is greater than the Promise callback in the Microtasks task. In other words, node clears the task in nextTick first, and then the task in Promise. To verify this conclusion, an example of a printed result is as follows:

/ * TODO: print order * / setTimeout (() = > {console.log ('setTimeout execution')}, 0) const p = new Promise ((resolve) = > {console.log ('Promise execution') resolve ()}) p.then (() = > {console.log ('Promise callback execution')}) process.nextTick (() = > {console.log ('nextTick execution')}) console.log ('Code execution completed')

What is the order of execution in the nodejs in the code block above?

Effect:

Print result: Promise execution-> Code execution completed-> nextTick execution-> Promise callback execution-> setTimeout execution

Explanation: it's easy to understand why this is printed. In the main code event loop, Promise execution and code execution are printed first, nextTick is placed in the nextTick queue, Promise callbacks are placed in the Microtasks queue, and setTimeout is placed in the timer heap. Next, the main loop is completed and starts to clear the contents of the two queues. First, clear the nextTick queue, nextTick execution is printed, then clear Microtasks queue, Promise callback execution is printed, and finally determine that there are timer tasks in the event loop loop. Then start a new event loop, first execute the timer task, and setTimeout execution is printed. The whole process is over.

Whether it is a task in nextTick or a task in promise, the code in both tasks will block the orderly progress of the event loop, resulting in the starvation of Ibank O, so you need to be careful with the logic in both tasks. For example, as follows:

/ * TODO: blocking Iamp O case * / process.nextTick () = > {const now = + new Date () / * blocking code for three seconds * / while (+ new Date ())

< now + 3000 ){}})fs.readFile('./file.js',()=>

{console.log ('Ishock O: file')}) setTimeout (() = > {console.log ('setTimeout:')}, 0)

Effect:

After three seconds, the timer task and the Imax O task in the event loop are executed in an orderly manner. In other words, the code in nextTick blocks the orderly progress of the event loop.

3 event cycle flow chart

Next, a flowchart is used to show the execution sequence of the six stages of the event loop and the execution logic of the two priority queues.

4 timer phase-> timer timer / delay interval

Timer watcher (Expired timers and intervals): timer watchers are used to check for asynchronous tasks created by setTimeout or setInterval. The internal principle is similar to asynchronous Imax O, but the timer / delayer internal implementation does not use thread pools. The timer object through setTimeout or setInterval will be inserted into the binary minimum heap inside the timer watcher. During each event loop, the timer object will be taken out from the top of the binary minimum heap to determine whether the timer/interval has expired, and if so, then call it to dequeue. Then check the first one of the current queue until there is no expiration, move to the next phase.

How the libuv layer handles timer

First, let's take a look at how the libuv layer handles timer.

Libuv/src/timer.c

Void uv__run_timers (uv_loop_t* loop) {struct heap_node* heap_node; uv_timer_t* handle; for (;;) {/ * find the root node in timer_heap in loop (minimum) * / heap_node = heap_min ((struct heap*) & loop- > timer_heap); / * / if (heap_node = = NULL) break Handle = container_of (heap_node, uv_timer_t, heap_node); if (handle- > timeout > loop- > time) / * execution time is longer than event loop events, then there is no need to execute * / break; uv_timer_stop (handle) in this loop; uv_timer_again (handle); handle- > timer_cb (handle);}}

As mentioned above, handle timeout can be understood as the expiration time, that is, the execution time when the timer returns to the function.

When the timeout is greater than the start time of the current event loop, it means that the callback function should not be executed yet. Then according to the nature of the binary minimum heap, the parent node is always smaller than the child node, so if the time node of the root node does not meet the execution time, the other timer does not meet the execution time. At this point, exit the callback function execution of the timer phase and go directly to the next stage of the event loop.

When the expiration time is less than the start time of the current event loop tick, it indicates that at least one expired timer exists, then the loop iterates over the root node of the minimum heap of the timer and calls the callback function corresponding to the timer. Each iteration of the loop updates the timer in which the root node of the minimum heap is the most recent node.

These are the characteristics of the execution of the timer phase in libuv. Next, we will analyze how to deal with timer delayers in node.

How the node layer handles timer

In Nodejs, setTimeout and setInterval are implemented by nodejs itself. Let's take a look at the implementation details:

Node/lib/timers.js

Function setTimeout (callback,after) {/ /... / * judge parameter logic * /.. / * create a timer observer * / const timeout = new Timeout (callback, after, args, false, true); / * insert the timer observer into the timer heap * / insert (timeout, timeout._idleTimeout); return timeout;}

SetTimeout: the logic is simple: create a timer time watcher and put it in the timer heap.

So what did Timeout do?

Node/lib/internal/timers.js

Function Timeout (callback, after, args, isRepeat, isRefed) {after * = 1 if (! (after > = 1 & & after {console.log ('setTimeout1:') process.nextTick (() = > {console.log (' nextTick')})}, 0) setTimeout () = > {console.log ('setTimeout2:')}, 0)

Print the results:

The nextTick queue is executed at the end of each phase of the event cycle, and the thresholds of both timers are 0. If you finish executing the expired task at one time in the timer phase, then print setTimeout1-> setTimeout2-> nextTick, actually execute a timer task first, then execute the nextTick task, and then execute the next timer task.

Precision problem: with regard to the counter problem of setTimeout, the timer is not accurate. Although the event loop in nodejs is very fast, from the creation of the delay timeout class, it will take up some events, and then the execution of the context, the execution of the nextTick queue, the execution of the Microtasks, will block the execution of the delay. Even when checking for timer expiration, it consumes some cpu time.

Performance problem: if you want to use setTimeout (fn,0) to perform some tasks that are not immediately called, then the performance is not as good as process.nextTick. First of all, the precision of setTimeout is not enough, and there is a timer object in it, which needs to be executed at the bottom of libuv, which takes up some performance, so you can use process.nextTick to solve this scenario.

5 pending Pha

The pending phase is used to handle the Istroke O callback function that is delayed before this event loop. First take a look at the timing of execution in libuv.

Libuv/src/unix/core.c

Static int uv__run_pending (uv_loop_t* loop) {QUEUE* q; QUEUE pq; uv__io_t* w / * pending_queue is empty, empty the queue, and return 0 * / if (QUEUE_EMPTY (& loop- > pending_queue)) return 0; QUEUE_MOVE (& loop- > pending_queue, & pq); while (! QUEUE_EMPTY (& pq)) {/ * pending_queue is not empty. Clear the callback. Return 1 * / Q = QUEUE_HEAD (& pq); QUEUE_REMOVE (Q); QUEUE_INIT (Q); w = QUEUE_DATA (Q, uv__io_t, pending_queue); w-> cb (loop, w, POLLOUT);} return 1;}

If the pending_queue of the task that holds the callback to Icano is empty, then 0 is returned directly.

If pending_queue has a callback task called back to O, then the callback task is performed.

6 idle, prepare Pha

Idle does some libuv and some internal operations, and prepare does some preparation for the next Imax O poll. Next, let's parse the more important poll phase.

7 poll I / O polling Pha

Before formally explaining what the poll phase does, let's first take a look at the execution logic of the polling phase in libuv:

Timeout = 0; if ((mode = = UV_RUN_ONCE & &! ran_pending) | | mode = = UV_RUN_DEFAULT) / * calculate timeout * / timeout = uv_backend_timeout (loop); / * go to Imer O polling * / uv__io_poll (loop, timeout)

Initialize the timeout timeout = 0, and calculate the timeout of this poll phase through uv_backend_timeout. The timeout will affect the execution of the asynchronous Ipool O and subsequent event loops.

What does timeout stand for?

First of all, you need to understand what the different timeout means in the IPUP O poll.

When timeout = 0, it means that the poll phase does not block the event loop, which means that there are more urgent tasks to perform. Then the current poll phase will not block, will move to the next phase as soon as possible, end the current tick as soon as possible, and enter the next event cycle, then these urgent tasks will be performed.

When timeout =-1, the specification blocks the event loop all the time, so you can stay in the poll phase of the asynchronous I _ hand O and wait for the new I _ hand O task to complete.

When timeout is equal to a constant, it indicates how long the io poll cycle can stay, then when there will be a timeout as a constant will be revealed immediately.

Get timeout

Timeout is obtained through uv_backend_timeout, so how do you get it?

Int uv_backend_timeout (const uv_loop_t* loop) {/ * the current event loop task stops without blocking * / if (loop- > stop_flag! = 0) return 0; / * when the current event loop loop is not active, it does not block * / if (! uv__has_active_handles (loop) & &! uv__has_active_reqs (loop)) return 0 / * when the queue of idle handles is not empty, 0 is returned, that is, there is no blocking. * / if (! QUEUE_EMPTY (& loop- > idle_handles)) return 0; / * when the pending queue is not empty. * / if (! QUEUE_EMPTY (& loop- > pending_queue) return 0; / * about closed callback * / if (loop- > closing_handles) return 0; / * calculate whether there is a delay timer with minimum delay | timer * / return uv__next_timeout (loop);}

The main things uv_backend_timeout does are:

Does not block when the current event loop stops.

When the current event loop loop is not active, it does not block.

When the idle queue (setImmediate) is not empty, 0 is returned without blocking.

There is no blocking when the iCompact o pending queue is not empty.

When it comes to closed callback functions, there is no blocking.

If none of the above is satisfied, then uv__next_timeout calculates whether there is a timer with the lowest delay threshold | delay timer (most urgent), and returns the delay time.

Next, take a look at the uv__next_timeout logic.

Int uv__next_timeout (const uv_loop_t* loop) {const struct heap_node* heap_node; const uv_timer_t* handle; uint64_t diff; / * find the timer * / heap_node = heap_min ((const struct heap*) & loop- > timer_heap) with the lowest latency; if (heap_node = = NULL) / * if there is no timer, return-1 and keep entering poll state * / return-1 Handle = container_of (heap_node, uv_timer_t, heap_node); / * if there is an expired timer task, then the return phase does not block * / if (handle- > timeout time) return 0; / * the timer that returns the current minimum threshold is subtracted from the events of the current event loop, and the time obtained can prove how long the poll can stay * / diff = handle- > timeout-loop- > time; return (int) diff }

What uv__next_timeout does is as follows:

Find the timer with the lowest time threshold (the highest priority), and if there is no timer, return-1. The poll phase will be unrestricted blocking. The advantage of this is that once the execution of IAccord O is finished, the callback function of IAccord O will be directly added to the poll, and then the corresponding callback function will be executed.

If there is timer, but timeout {console.log ('setImmediate1') process.nextTick (() = > {console.log (' nextTick')})}) setImmediate (() = > {console.log ('setImmediate2')}))

Print setImmediate1-> nextTick-> setImmediate2, execute one setImmediate in each event loop, then empty the nextTick queue, and execute another setImmediate2 in the next event loop.

SetImmediate execution flow chart

SetTimeout & setImmediate

Next, compare setTimeout and setImmediate, and if the developer expects to delay the execution of asynchronous tasks, then compare the difference between setTimeout (fn,0) and setImmediate (fn).

SetTimeout is used to execute the callback function within the minimum error of setting the threshold. There is a problem with the accuracy of setTimeout. Both the creation of setTimeout and poll may affect the execution of setTimeout callback function.

Immediately after the poll phase, setImmediate enters the check phase and executes the setImmediate callback.

If setTimeout and setImmediate are together, who executes it first?

First write a demo:

SetTimeout (() = > {console.log ('setTimeout')}, 0) setImmediate (() = > {console.log (' setImmediate')})

Guess

First guess, setTimeout occurs in the timer phase, setImmediate occurs in the check phase, timer phase is earlier than the check stage, then setTimeout takes precedence over setImmediate printing. But is that really the case?

Actual print result

From the above print results, the timing of setTimeout and setImmediate execution is uncertain, why this happens. As mentioned above, even if the second parameter of setTimeout is 0, setTimeout (fn,1) will be processed in nodejs. After the synchronization code of the main process is executed, it enters the event loop phase and enters the timer for the first time, when the time threshold of the timer corresponding to settimeout is 1. If in the previous uv__run_timer (loop), the total time consuming of system time call and time comparison does not exceed 1ms, no expired timer will be found in the timer phase, then the current timer will not be executed, and then the setImmediate callback will be executed in the check phase. The order of execution at this time is: setImmediate-> setTimeout.

However, if the total time is more than one millisecond, the order of execution will change. In the timer phase, take out the expired setTimeout tasks for execution, and then go to the check phase, and then execute setImmediate, where setTimeout-> setImmediate.

The reason for this is that the interval between timer's time check and the current event loop tick may be less than the 1ms or greater than the 1ms threshold, so it determines whether the setTimeout is executed in the first event loop.

The next case where I block with code will most likely cause setTimeout to always take precedence over setImmediate execution.

/ * TODO: setTimeout & setImmediate * / setImmediate (() = > {console.log ('setImmediate')}) setTimeout (() = > {console.log (' setTimeout')}, 0) / * block the code with 100000 loops to cause the setTimeout to expire * / for (let item0 / I setImmediate).

Special case: determine the sequence consistency. Let's take a look at the special situation.

Const fs = require ('fs') fs.readFile ('. / file.js', () = > {setImmediate () = > {console.log ('setImmediate')}) setTimeout (() = > {console.log (' setTimeout')}, 0)}))

As a result of the above situation, setImmediate always takes precedence over setTimeout execution, as to why, let's analyze the reason.

First take a look at the asynchronous task-- there is an asynchronous Imax O task in the main process, and there is a setImmediate and a setTimeout in the Imax O callback.

During the poll phase, a callback is performed on Ithumb O. And then deal with a setImmediate

As long as the characteristics of each stage are mastered, then the implementation of different situations can be clearly distinguished.

9 close Pha

The close phase is used to perform some callback functions that are closed. Execute all close events. Next, take a look at the implementation of the close event libuv.

Libuv/src/unix/core.c

Static void uv__run_closing_handles (uv_loop_t* loop) {uv_handle_t* p; uv_handle_t* Q; p = loop- > closing_handles; loop- > closing_handles = NULL; while (p) {Q = p-> next_closing; uv__finish_close (p); p = Q;}}

The uv__run_closing_handles method iterates through the callback function in the close queue.

Summary of 10 Nodejs event cycle

Let's summarize the Nodejs event loop.

The event cycle of Nodejs is divided into six stages. They are timer stage, pending stage, prepare stage, poll stage, check stage and close stage.

NextTick queue and Microtasks queue execution characteristics, after each phase is completed, nextTick priority is higher than Microtasks (Promise).

The poll phase mainly deals with Imax O, and if there are no other tasks, it will be in the polling blocking phase.

The timer phase mainly deals with timers / delayers, which are not accurate and require additional performance waste to create, and their execution is affected by the poll phase.

The pending phase handles the callback task for which Imax O expires.

The check phase deals with setImmediate. Timing and difference between setImmediate and setTimeout.

Exercise exercise of Nodejs event cycle

Next, in order to have a better understanding of the event loop process, here are the problems of two event cycles. As a practice:

Exercise 1: function. NextTick (function () {console.log ('1');}); process.nextTick (function () {console.log ('2'); setImmediate (function () {console.log ('3');}); process.nextTick (function () {console.log ('4');}); setImmediate (function () {console.log ('5')) Process.nextTick (function () {console.log ('6');}); setImmediate (function () {console.log ('7');}); setTimeout (e = > {console.log (8); new Promise ((resolve,reject) = > {console.log (8)); resolve ();}) .then (e = > {console.log) )}, 0) setTimeout (e = > {console.log (9);}, 0) setImmediate (function () {console.log ('10'); process.nextTick (function () {console.log (' 11');}); process.nextTick (function () {console.log ('12');}); setImmediate (function () {console.log (' 13');});}) Console.log ('14'); new Promise ((resolve,reject) = > {console.log (15); resolve ();}) .then (e = > {console.log (16);})

If you just read this demo, you can be confused, but the above mentioned event cycle is very easy. Let's take a look at the overall process:

The first phase: start the js file first, then enter the first event loop, then the synchronization task will be executed first:

Print first:

Print console.log ('14')

Print console.log (15)

NextTick queue:

NextTick-> console.log (1) nextTick-> console.log (2)-> setImmediate (3)-> nextTick (4)

Promise queue

Promise.then (16)

Check queue

SetImmediate (5)-> nextTick (6)-> setImmediate (7) setImmediate (10)-> nextTick (11)-> nextTick (12)-> setImmediate (13)

Timer queue

SetTimeout (8)-> promise (8)-> promise.then (8) setTimeout (9)

The second phase: before entering a new event loop, empty the nextTick queue and the promise queue in the order that the nextTick queue is larger than the Promise queue.

Clear nextTick and print:

Console.log ('1')

Console.log ('2')

When the second nextTick is executed, there is another nextTick, so this nextTick is also added to the queue. It will be carried out immediately.

Console.log ('4')

Next, clear the Microtasks.

Console.log (16)

A new setImmediate is added to the check queue at this time.

Check queue setImmediate (5)-> nextTick (6)-> setImmediate (7) setImmediate (10)-> nextTick (11)-> nextTick (12)-> setImmediate (13) setImmediate (3)

Then enter a new event loop and first perform the tasks in timer. Execute the first setTimeout.

Execute the first timer:

Console.log (8)

A Promise is found. In the context of normal execution:

Console.log (8 million million e')

Then add Promise.then to the nextTick queue. The nextTick queue will be emptied immediately next.

Console.log (8 days after birth)

Execute the second timer:

Console.log (9)

Next comes the check phase, where you execute the contents of the check queue:

Execute the first check:

Console.log (5)

A nextTick is found, and then a setImmediate adds setImmediate to the check queue. Then execute nextTick.

Console.log (6)

Execute the second check

Console.log (10)

Two nextTick and one setImmediate are found. Next, clear the nextTick queue. Add setImmediate to the queue.

Console.log (11)

Console.log (12)

The check queue at this time looks like this:

SetImmediate (3) setImmediate (7) setImmediate (13)

Next, empty the check queue sequentially. Printing

Console.log (3)

Console.log (7)

Console.log (13)

At this point, execute the entire event loop. Then the overall print content is as follows:

The above is how to understand asynchronous Iripple O and event loop in Nodejs shared by Xiaobian. If you happen to have similar doubts, please refer to the above analysis to understand. If you want to know more about it, you are welcome to follow the industry information channel.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report