what's the differences between these two async func in nodejs?

Viewed 83
const fs = require("fs");

fs.readFile("aa.js", () => {
  console.log("1");
  process.nextTick(() => {
    console.log("3");
  });
});

fs.readFile("aa.js", () => {
  console.log("2");
  process.nextTick(() => {
    console.log("4");
  });
});

// the result is 1 3 2 4

const net = require("net");
const server = net.createServer(() => {}).listen(8080);

server.on("listening", () => {
  console.log("1");
  process.nextTick(() => {
    console.log("3");
  });
});

server.on("listening", () => {
  console.log("2");
  process.nextTick(() => {
    console.log("4");
  });
});

// the result is 1 2 3 4

IMO, these two async callback should behave the same, but the result is different, what's the reason behind the scene?

1 Answers

The first one is a race between two completely separate asynchronous fs.readFile() operations. Whichever one completes first is likely to get both of it's console logs before the other. Because these are operations that take some measurable amount of time and they both have to do the exact same amount of work, it's likely that one you started first will finish first and that's what you're seeing. But, technically, it's an indeterminate race between the two asynchronous operations and they could finish in any order. Since one is likely to finish slightly before the other, it's completion callback will be called before the other and it's also likely that the 2nd one won't yet be done before the next tick happens so that's why you see both log messages from whichever one finishes first.

Your second one is two event listeners for the exact same event. So, those two listeners are guaranteed to be called on the same tick one after the other. When an event listener object emits an event, it synchronously calls all the listeners for that event one after the other, all on the same tick. That's why you get 1 and then 2 before 3 and 4 which occur on the future ticks.

One should not confuse an eventEmitter object with the event queue. They are not the same thing. In this case, your server object is a subclass of an eventEmitter. Some code internal to the server decides to emit the listening event to listeners of the server's eventEmitter. That decision to emit the event was likely the result of some asynchronous operation that came from the event queue. But, to actually emit to the eventEmitter, this is just synchronous function calls to the registered listeners. The event queue is not involved in this. Internal to the eventEmitter code, it literally has a for loop that loops through the matching event handlers and calls each one, one after the other. That's why you get 1, then 2.

In fact, here's the code reference inside the .emit() method on the EventEmitter class definition that shows looping through the matching listeners and calling them synchronously. And, here's a snippet of that code calling each listener one after the other:

const len = handler.length;
const listeners = arrayClone(handler, len);
for (var i = 0; i < len; ++i)
  Reflect.apply(listeners[i], this, args);

}

Related