Node: Event loop tick id

Created on 20 Mar 2020  路  6Comments  路  Source: nodejs/node

Basically looking for an event loop tick id, for this purpose:

const run = (cb) => {

   let isCurrentTick = true;
   process.nextTick(() => {
      isCurrentTick = false;
   });

  doSomeWork(() =>{
      isCurrentTIck ? process.nextTick(cb) : cb();
  });

}

this is an optimization that can reduce the memory overhead of servers. It's mostly about convenience, but in some cases very necessary when you don't know for sure if something is being fired asynchronously or not.

so the fix looks like:

const run = (cb) => {

  const currentEventLoopId = process.getEventTickId();

  doSomeWork(() =>{
      currentEventLoopId  === process.getEventTickId() ? process.nextTick(cb) : cb();
  });

}

so what I am looking for is an incrementing id for every tick of the event loop. This fix would obviate the need for using process.nexTick calls to store some local boolean variable as in the first code example.

async_hooks question

All 6 comments

I think you have a bit of a misunderstanding of how process.nextTick() works. Based on the use case you describe, I think the async_hooks module already provides what you need:

Take the following code for instance:

const {
  executionAsyncId,
  triggerAsyncId
} = require('async_hooks');

console.log('Execution Async ID:', executionAsyncId());
console.log('Trigger Async ID:', triggerAsyncId());

function one(cb) {
  cb();
}

function two(cb) {
  setImmediate(cb);
}

one(() => {
  console.log('Synchronous Execution: ');
  console.log('Execution Async ID:', executionAsyncId());
  console.log('Trigger Async ID:', triggerAsyncId());
});

two(() => {
  console.log('Asynchronous Execution: ');
  console.log('Execution Async ID:', executionAsyncId());
  console.log('Trigger Async ID:', triggerAsyncId());
});

Function one() is synchronous, function two() will execute the callback at the start of the next turn of the event loop.

The executionAsyncId() identifies the current execution context, whereas the triggerAsyncId() identifies the execution context that triggered (or caused) the current context. Run this using node ex.js and you'll get the output:

Execution Async ID: 1
Trigger Async ID: 0
Synchronous Execution:
Execution Async ID: 1
Trigger Async ID: 0
Asynchronous Execution:
Execution Async ID: 5
Trigger Async ID: 1

In the first two lines, you'll see that the Execution Async ID is 1 and the trigger is 0. This will always be the case for the entry point JavaScript that you run. Notice that in the output for function one(), the Execution ID and the Trigger ID do not change. When you invoke a callback, if the execution ID does not change, the function was executed synchronously.

Note that in the output for function two(), the trigger async ID identifies the execution async ID that caused that callback to be scheduled for execution.

Likewise, using nextTick() you can do the same:

const {
  executionAsyncId,
  triggerAsyncId
} = require('async_hooks');

console.log('Execution Async ID:', executionAsyncId());
console.log('Trigger Async ID:', triggerAsyncId());

process.nextTick(() => {
  console.log('\nNext tick: ');
  console.log('Execution Async ID:', executionAsyncId());
  console.log('Trigger Async ID:', triggerAsyncId());
});

Which, when run, yields:

Execution Async ID: 1
Trigger Async ID: 0

Next tick:
Execution Async ID: 5
Trigger Async ID: 1

With Promises, things get a bit weird...

const {
  executionAsyncId,
  triggerAsyncId
} = require('async_hooks');

console.log('Execution Async ID:', executionAsyncId());
console.log('Trigger Async ID:', triggerAsyncId());

(new Promise((res) => {
  console.log('\nPromise Setup: ');
  console.log('Execution Async ID:', executionAsyncId());
  console.log('Trigger Async ID:', triggerAsyncId());
  res();
})).then(() => {
  console.log('\nPromise Then: ');
  console.log('Execution Async ID:', executionAsyncId());
  console.log('Trigger Async ID:', triggerAsyncId());
}).then(() => {
  console.log('\nPromise Then: ');
  console.log('Execution Async ID:', executionAsyncId());
  console.log('Trigger Async ID:', triggerAsyncId());
});

Which, when run, yields:

Execution Async ID: 1
Trigger Async ID: 0

Promise Setup:
Execution Async ID: 1
Trigger Async ID: 0

Promise Then:
Execution Async ID: 0
Trigger Async ID: 0

Promise Then:
Execution Async ID: 0
Trigger Async ID: 0

@jasnell thanks, why don't the numbers increment when doing promises?

The microtaskqueue (the thing that executes the Promise handlers) is a bit of a special beast. It is executed each time the process.nextTick queue is drained but it is handled more by V8 than by Node.js. Because they are different, we track them in a slightly different way with async_hooks.

promises are only tracked if at least one async_hook as been created (e.g. createHook({init: () => {}}).enable()) see https://nodejs.org/dist/latest-v13.x/docs/api/async_hooks.html#async_hooks_promise_execution_tracking

Yes that 鈽濓笍 :-)

oh shizzz

Was this page helpful?
0 / 5 - 0 ratings