Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript async callbacks - Promise and setTimeout [duplicate]

In the following code:

setTimeout(() => console.log("hello"), 0);

Promise.resolve('Success!')
  .then(console.log)

What should happen in my understanding:

  1. setTimeout is called => print hello directly added to callback queue as time is 0
  2. Promise.resolve => print Success! added to callback queue

If I am not wrong, the callback queue is FIFO.

But the code output is:

Success!
hello

What is the explanation?

like image 884
Logan Wlv Avatar asked Feb 26 '21 13:02

Logan Wlv


People also ask

Can we use async await with setTimeout?

To slow down or delay processing in JavaScript, setTimeout function can be combined with async/await keywords to let our program take some breath before continuing to the next instruction.

Does setTimeout callback 0 execute the callback synchronously or asynchronously?

setTimeout is synchronous in nature. Its callback get registered by event loop in timer phase which will be executed an asynchronous manner.

Is callback and promise same?

A callback function is passed as an argument to another function whereas Promise is something that is achieved or completed in the future. In JavaScript, a promise is an object and we use the promise constructor to initialize a promise.

What is difference between callbacks promises and async await in JavaScript?

Await eliminates the use of callbacks in . then() and . catch(). In using async and await, async is prepended when returning a promise, await is prepended when calling a promise.


2 Answers

There are two different queues involved here: a Task queue and a Microtask queue.

Callback functions scheduled using setTimeout are added in the task queue whereas the callbacks scheduled using promises are added in the microtask queue or a job queue.

A microtask queue is processed:

  • after each callback as long as the call-stack is empty.
  • after each task.

Also note that if a microtask in a microtask queue queues another microtask, that will also be processed before processing anything in the task queue. In other words, microtask queue will be processed until its empty before processing the next task in the task queue.

The following code snippet shows an example:

setTimeout(() => console.log('hello'), 0);

Promise.resolve('first microtask')
  .then(res => {
    console.log(res);
    return 'second microtask';
  })
  .then(console.log);

In your code, callback function of setTimeout is added to the task queue and the Promise.resolve queues a micro-task in a microtask queue. This queue is processed at the end of the script execution. That is why "success" is logged before "hello".

The following image shows a step-by-step execution of your code:

Enter image description here

Resources for further reading:

  • Tasks, microtasks, queues and schedules

  • JavaScript job queue and microtasks

like image 95
Yousaf Avatar answered Nov 08 '22 14:11

Yousaf


There are 2 separate queues for handling of the callbacks. A macro and a micro queue. setTimeout enqueues an item in the macro queue, while promise resolution - to the micro queue. The currently executing macro task(the main script itself in this case) is executed synchronously, line by line until it is finished. The moment it is finished, the loop executes everything queued in the microtask queue before continuing with the next item from the macro queue(which in your case is the console.log("hello") queued from the setTimeout).

Basically, the flow looks like this:

  1. Script starts executing.

MacrotaskQueue: [], MicrotaskQueue: [].

  1. setTimeout(() => console.log("hello"), 0); is encountered which leads to pushing a new item in the macrotask queue.

MacrotaskQueue: [console.log("hello")], MicrotaskQueue: [].

  1. Promise.resolve('Success!').then(console.log) is read. Promise resolves to Success! immediately and console.log callback gets enqueued to the microtask queue.

MacrotaskQueue: [console.log("hello")], MicrotaskQueue: [console.log('Success!')].

  1. The script finishes executing so it checks if there is something in the microtask queue before proceeding with the next task from the macro queue.
  2. console.log('Success!') is pulled from the microtask queue and executed.

MacrotaskQueue: [console.log("hello")], MicrotaskQueue: [].

  1. Script checks again if there is something else in the microtask queue. There is none, so it fetches the first available task from the macrotask queue and executes it, namely - console.log("hello").

MacrotaskQueue: [], MicrotaskQueue: [].

  1. After the script finishes executing the console.log("hello"), it once again checks if there is anything in the microtask queue. It is empty, so it checks the macrotask queue. It is empty as well so everything queued is executed and the script finishes.

This is a simplified explanation, though, as it can get trickier. The microtask queue normally handles mainly promise callbacks, but you can enqueue code on it yourself. The newly added items in the microtask queue will still be executed before the next macrotask item. Also, microtasks can enqueue other microtasks, which can lead to an endless loop of processing microtasks.

Some useful reference resources:

  • The event loop
  • Microtasks
  • Using Microtasks
like image 20
zhulien Avatar answered Nov 08 '22 14:11

zhulien