Event loop
Javascript runs in a different way based on Event Loop. Microtask queue and task queue confuse many people why the code runs like this. Here's a look at the Event Loop.
Execution process of event loop
ECMA-262
As the standard behind Javascript, ECMA-262 does not define Event Lopp, no microtask queue and task queue. (ECMA-262 also does not define timing functions such as setTimeout
, nor does it have I/O.)
It defines Job and Job queue, and has a top-level [RunJobs] operation:
- Perform ? InitializeHostDefinedRealm().
In an implementation-dependent manner, obtain the ECMAScript source texts (see clause 10) and any associated host-defined values for zero or more ECMAScript scripts and/or ECMAScript modules. For each such sourceText and hostDefined, do
- If sourceText is the source code of a script, then
i. Perform EnqueueJob("ScriptJobs", ScriptEvaluationJob, « sourceText, hostDefined »).- Else sourceText is the source code of a module,
ii. Perform EnqueueJob("ScriptJobs", TopLevelModuleEvaluationJob, « sourceText, hostDefined »).Repeat,
- Suspend the running execution context and remove it from the execution context stack.
- Assert: The execution context stack is now empty.
- Let nextQueue be a non-empty Job Queue chosen in an implementation-defined manner. If all Job Queues are empty, the result is implementation-defined.
- Let nextPending be the PendingJob record at the front of nextQueue. Remove that record from nextQueue.
- Let newContext be a new execution context.
- Set newContext's Function to null.
- Set newContext's Realm to nextPending.[[Realm]].
- Set newContext's ScriptOrModule to nextPending.[[ScriptOrModule]].
- Push newContext onto the execution context stack; newContext is now the running execution context.
- Perform any implementation or host environment defined job initialization using nextPending.
- Let result be the result of performing the abstract operation named by nextPending.[[Job]] using the elements of nextPending.[[Arguments]] as its arguments.
- If result is an abrupt completion, perform HostReportErrors(« result.[[Value]] »).
Here, according to the type of script, first add ScriptEvaluationJob or TopLevelModuleEvaluationJob to the ScriptJob
queue. Then start to loop through each non-empty Job queue.
Within the same Job queue, tasks are strictly first-in, first-out. However, which one to choose is an implementation decision. However, ECMA-262 only defines two Job queues, one is the top-level ScriptJob
above, which is only used to put (the only one) top-level task; the other is PromiseJob
, which is used for Promises. The two Job queues will not be non-empty at the same time, so the execution order is actually deterministic.
EnqueuJob is used to join the Job to the Job queue.
HTML
The Javascript used in HTML does not use the above-mentioned RunJobs and Job queue, but defines its own Event Loop , and task queue , microtask queue .
HTML's Event performs the following :
- Let taskQueue be one of the event loop's task queues, chosen in a user-agent-defined manner, with the constraint that the chosen task queue must contain at least one runnable task. If there is no such task queue, then jump to the microtasks step below.
- Let oldestTask be the first runnable task in taskQueue, and remove it from taskQueue.
Report the duration of time during which the user agent does not execute this loop by performing the following steps:
- Set event loop begin to the current high resolution time.
- If event loop end is set, then let top-level browsing contexts be the set of all top-level browsing contexts of all Document objects associated with the event loop. Report long tasks, passing in event loop end, event loop begin, and top-level browsing contexts.
- Set the event loop's currently running task to oldestTask.
- Perform oldestTask's steps.
- Set the event loop's currently running task back to null.
- Remove oldestTask from its task queue.
- Microtasks: Perform a microtask checkpoint.
- Let now be the current high resolution time. [HRT]
Report the task's duration by performing the following steps:
- ...
- Omitted below
Note that microtask queue is not a task queue. task queue is not a queue because it is not first in first out. It can be seen from the first step that the task taken from the task queue is not necessarily the first task to enter the task queue.
In the Event Loop, after completing a task queue task, Perform a microtask checkpoint :
- If the event loop's performing a microtask checkpoint is true, then return.
- Set the event loop's performing a microtask checkpoint to true.
While the event loop's microtask queue is not empty:
- Let oldestMicrotask be the result of dequeuing from the event loop's microtask queue.
- Set the event loop's currently running task to oldestMicrotask.
- Run oldestMicrotask.
- Set the event loop's currently running task back to null.
- For each environment settings object whose responsible event loop is this event loop, notify about rejected promises on that environment settings object.
- Cleanup Indexed Database transactions.
- Set the event loop's performing a microtask checkpoint to false.
Here, all tasks in the microtask queue (including the tasks newly entered into the microtask queue during this process) will be executed in a first-in, first-out order, until the microtask queue is empty.
So the execution process is to execute a task of the task queue, then execute all the tasks in the microtask queue, and then enter the next cycle of the Event Loop, and then execute a task in the task queue.
Jobs added by EnqueuJob in ECMA-262, 1622c05b8acc3b all enter the microtask queue in ).
If there are only microtasks generated by Promise, the above execution process is basically the same as that of RunJobs. Therefore, the subsequent discussions are based on the definition of HTML's Event Loop and microtask/task.
Task vs Microtask
So, what are the differences between microtask and task? Here is a part to introduce, which should solve many online "why the output is like this" questions.
Microtask
All Promise related jobs in ECMA-262. include:
Callback for
then
,catch
- If the Promise has been settled (the state has been determined) when calling
then
orcatch
, then the corresponding callback function is directly added to the microtask queue, see PerformPromiseThen . Otherwise, go back to being recorded and join the micro task queue via TriggerPromiseActions when the Promise settles. await
is implemented by Promise, eachawait
will execute the operation after await through Promise.then (even if the object ofawait
is not a Promise). (See Await )
- If the Promise has been settled (the state has been determined) when calling
When a Promise (P1) resolves another Promise (P2), a call to
P2.then
is automatically generated. The call will be added to the microtask queue.- See also Promise Resolve Functions
P2.then
is executed, the callback ofP2.then
will become another microtask- The callback of the
P2.then
will resolve or reject P1
- Mutation Observers
Task
Callback for
SetTimeout
,SetInterval
. Even if the time is set to0
.- See timer initialisation steps
- The callback will be added to the task queue after the delay is completed.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。