The experiment

Let’s start with an experiment: Which executes faster, an immediate Promise or an immediate timeout (a 0 millisecond timeout)?

Promise.resolve(1).then(function resolve() {
  console.log('Resolved! ');
});

setTimeout(function timeout() {
  console.log('Timed out! ');
}, 0);

// logs 'Resolved! '
// logs 'Timed out! '
Copy the code

Promise.resolve(1) is a static function that returns a Promise resolved immediately. SetTimeout (callback, 0) executes a callback with a delay of 0 milliseconds.

Open the execute and check console. You will see the log first printed with ‘Resolved! ‘, and then prints ‘Timeout Completed! ‘. Promises of immediate resolution are handled faster than immediate timeouts.

Because of the Promise. Resolve (true). Then (…). The setTimeout (… 0) was called before, so will promise processing be faster?

To modify the conditions, call setTimeout(… , 0) :

setTimeout(function timeout() {
  console.log('Timed out! ');
}, 0);

Promise.resolve(1).then(function resolve() {
  console.log('Resolved! ');
});

// logs 'Resolved! '
// logs 'Timed out! '
Copy the code

Execute and look at the console, the result is the same!

Although the setTimeout (… , 0) in Promise. Resolve (true). Then (…). It was called before, but ‘Resolved! ‘Still in ‘Timed out! ‘is printed before.

Experiments showed that promises resolved immediately were processed before the immediate timeout. So… Why is that?

Event loop

Questions related to asynchronous JavaScript can be answered by exploring the event loop. Let’s review how asynchronous JavaScript works.

The Call Stack is a LIFO (Last in, first out) structure for storing execution contexts created during code execution. In short, the call stack performs functions.

Web APIS are asynchronous operations (FETCH requests, Promises, timers) where callbacks wait for work to complete.

** Task Queue ** is a FIFO (first-in, first-out) structure that contains callbacks for asynchronous operations that are ready to be executed. For example, the setTimeout() callback (ready to execute) enters the task queue.

A job queue is a FIFO (first-in, first-out) structure that contains callbacks to promises ready to execute. For example, a resolved resolve or reject callback is put into the work queue.

Finally, the Event loop keeps an eye on whether the call stack is empty. If the call stack is empty, the event loop looks for the work queue or task queue and causes the callback queue ready for execution to be placed in the call stack.

Work queues and task queues

Let’s look at the previous experiment from the point of view of the event loop. I will analyze the code step by step.

  1. Call stack executionsetTimeout(... , 0)And “schedule” a timer.timeout()Callbacks are stored in the Web API:
setTimeout(function timeout() {  console.log('Timed out! '); },0);
Promise.resolve(1).then(function resolve() {
  console.log('Resolved! ');
});
Copy the code

  1. Call stack executionPromise.resolve(true).then(resolve)And “arrange” a promise resolution.resolved()Callbacks are stored in the Web API:
setTimeout(function timeout() {
  console.log('Timed out! ');
}, 0);

Promise.resolve(1).then(function resolve() {  console.log('Resolved! '); });Copy the code

  1. The promise is resolved immediately, and the timer runs out immediately. The timer is called backtimeout()Is “queued” to the task queue, the promise callbackresolve()To be queued to a work queue:

  1. Here’s the interesting part: the event cycle prioritizes the work over the task. The event loop causes the promise to call backresolve()It is dequeued from the work queue and put into the call stack, which then performs the Promise callbackresolve():
setTimeout(function timeout() {
  console.log('Timed out! ');
}, 0);

Promise.resolve(1).then(function resolve() {
  console.log('Resolved! '); });Copy the code

‘Resolved! ‘is printed to the console.

  1. Finally, the event loop calls back the timertimeout()Move from the task queue to the call stack. The stack is then called to perform the timer callbacktimeout():
setTimeout(function timeout() {
  console.log('Timed out! '); },0);

Promise.resolve(1).then(function resolve() {
  console.log('Resolved! ');
});
Copy the code

‘Timed out! ‘is printed to the console.

The call stack is empty. Script execution is complete.

conclusion

Why do promises that resolve immediately process faster than immediate timers?

It is the “priority” of the event loop that causes the task in the task queue (which stores the callback to implemented promises) to be unqueued from the task queue (which stores the setTimeout() callback to timeout).

Welcome to pay attention to my public number: front-end pioneer