0

The following code is from this medium article

const fastify = require("fastify")({ logger: true });
const crypto = require("crypto");
const randomstring = require("randomstring");

// Declare healthcheck ping-pong
fastify.get("/ping", (_request, reply) => {
  reply.send({ data: "pong" });
});

fastify.get("/event-loop-blocking-operation", async (_request, reply) => {
  let hash = crypto.createHash("sha256");
  const numberOfHasUpdates = 10e6;

  for (let iter = 0; iter < numberOfHasUpdates; iter++) {
    hash.update(randomstring.generate());
  }
  reply.send({ data: "Finished doing long task" });
});

fastify.get("/event-loop-nonblocking-operation", async (_request, reply) => {
  const hash = crypto.createHash("sha256");
  const numberOfHasUpdates = 10e6;

  const breathSpace = async (delayInS) =>
    new Promise((resolve, reject) => {
      setTimeout(() => resolve(), delayInS * 1000);
    });

  for (let iter = 0; iter < numberOfHasUpdates; iter++) {
    const hashed = hash.update(randomstring.generate());
    await breathSpace(0);
  }
  reply.send({ data: "Finished long process" });
});

// Run the server!
const port = 3000;
fastify.listen(port, (err, address) => {
  if (err) {
    fastify.log.error(err);
    process.exit(1);
  }
  fastify.log.info(`server listening on ${address}`);
});

case 1

#in Terminal 1
$ node blocking_event_loop.js

#in Terminal 2
#calls 127.0.0.1:3000/ping every one second
#keeps printing {data: "pong"} in terminal
$ while  true; do date; curl 127.0.0.1:3000/ping; sleep 1; echo \n; done;

#in Terminal 3
#blocks the event loop
$ date && curl 127.0.0.1:3000/event-loop-blocking-operation && date

After calling the last command, the event loop gets blocked and Terminal 2 stops printing.

case 2

#in Terminal 1
$ node blocking_event_loop.js

#in Terminal 2
#calls 127.0.0.1:3000/ping every one second
#keeps printing {data: "pong"} in terminal
$ while  true; do date; curl 127.0.0.1:3000/ping; sleep 1; echo \n; done;

#in Terminal 3
#does not block the event loop
$ date && curl 127.0.0.1:3000/event-loop-nonblocking-operation && date

In 2nd case, After calling the last command, Terminal 2 keeps printing {data: "pong"} and the operation defined in the /event-loop-blocking-operation's route handler keeps doing what it's doing.

How is it that calling await breathSpace(0) in the loop will run the operation in a non-blocking way?

As far as I know when the /event-loo-nonblocking-operation endpoint is called via curl:

  1. /event-loop-nonblocking-operation's corresponding route handler is enqueued to the I/O callback queue which is also known as poll queue
  2. route handler is dequeued from poll queue and pushed to call stack
  3. route handler's code is run line by line 3.1 hash.update method is called (on each iteration of for loop) 3.2 await breathSpace(0) is called (on each iteration of for loop) 3.2.1 the (resolve, reject) => setTimeout(() => resolve(), 0) is enqueued to the Timer queue

Now considering event loop not getting blocked, Does this mean, that even though the first iteration of the loop is not done, event loop changes it's phase from poll to timer to handle the setTimeout which resolves the promise? How does it do the for loop without blocking?

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Gandalf
  • 2,921
  • 5
  • 31
  • 44
  • Your case 2 and case 1 are the same. Suspecting this is a copy-pasta error, I'm not quire sure to understand what you are not understanding... The article explains quite simply what happens. `hash.update()` is synchronous so it will block the event loop, and in case 2 you are now awaiting for a timer task at each iteration, so the event loop isn't blocked anymore during the resolution of this timer. – Kaiido Apr 05 '22 at 01:21
  • Does this answer your question? [How does javascript async/await actually work?](https://stackoverflow.com/questions/57160341/how-does-javascript-async-await-actually-work) – Kaiido Apr 05 '22 at 01:27
  • @kaiido fixed the mistake – Gandalf Apr 05 '22 at 08:02
  • Yes, I know that. What I want to know is, how can it all be explained in terms of event loop and all its queues. How does awaiting for a timer task at each iteration, unblocks the event loop? Is there any rule that says that when a timer is awaited, the phase should change to timer phase? – Gandalf Apr 05 '22 at 08:09
  • 2
    That's just how async await works. When the engine hits an `await` clause it will pause the current script execution, give the control back to the event-loop until the awaited Promise resolves, where the remaining of the "paused" script will resume in a microtask. So yes, until the Promise resolves the event-loop will go through all its cycles. It's no different than `doSomethingAsync().then(() => continueScript())`. while `doSomethingAsync` does its async stuff, the event-loop just loops. – Kaiido Apr 05 '22 at 08:23
  • So event loop is in poll phase, it pops the callback of the promise(which is resolved in setTimeout) from microTask queue and pauses on the await line, and based on what you are saying, gives the control back to EL, since the poll queue already contains callbacks from ping route, it exausts the queue and goes to other phases one by one. till it reaches the timer queue and etc? – Gandalf Apr 05 '22 at 08:35
  • Not sure what "callback of the promise" you are talking about, the Promise constructor's executor is ran synchronously. The call to `setTimeout()` schedule a task to be queued, and there the call to `resolve()` will queue a microtask to execute the remaining of the paused script. – Kaiido Apr 05 '22 at 08:47

0 Answers0