While reading the source code for MQtt.js, I came across a very confusing piece of code. NextTick (work) is called in nextTickWork, where the function work calls nextTickWork. How is this recursive? It’s kind of like an endless loop, right? NextTick () : process.nexttick ()

writable._write = function (buf, enc, done) {
    completeParse = done
    parser.parse(buf)
    work() / / start nextTick
}
function work () {
  var packet = packets.shift()
  if (packet) {
    that._handlePacket(packet, nextTickWork) // Notice here
  } else {
    var done = completeParse
    completeParse = null
    if (done) done()
  }
}
function nextTickWork () {
  if (packets.length) {
    process.nextTick(work) // Notice here
  } else {
    var done = completeParse
    completeParse = null
    done()
  }
}
Copy the code
  • I metprocess.nextTick()
    • Syntax (callback and optional ARgs)
    • process.nextTick()knowledge
    • process.nextTick()Use the sample
      • The simplest example
      • process.nextTick()Can be used to control code execution order
      • process.nextTick()The API can be fully asynchronous
  • How to understand process.nexttick ()?
  • Why is process.Nexttick () a more powerful asynchronous expert?
    • Process.nexttick () delays calls more strictly than setTimeout()
    • Process.nexttick () solves the actual problem
  • Why process.nexttick ()?
    • Allows the user to handle errors, clean up unwanted resources, or try the request again before the event loop
    • Sometimes make sure that the callback is called after calling the stack unwound and before the Event loop resumes
  • Review the

I metprocess.nextTick()

Syntax (callback and optional ARgs)

process.nextTick(callback[, ...args])
Copy the code
  • Callback callback function
  • Args The extra argument passed when calling callback

process.nextTick()knowledge

  • process.nextTick()Callback added to “Next Tick Queue”
  • “Next Tick Queue” will queue in FIFO after the current JavaScript stack execution and before the next Event loop execution
  • If you recursively callprocess.nextTick()It can lead to an infinite loop that needs to be terminated at the right time.
  • process.nextTick()Can be used to control code execution order. Ensure that the method is invoked after the object completes constructor but before I/O occurs.
  • process.nextTick()The API can be fully asynchronous. It is important that the API be either 100% synchronous or 100% asynchronous and can passprocess.nextTick()To live up to that promise

process.nextTick()Use the sample

  • The simplest example
  • process.nextTick()This is important for API development
The simplest example
console.log('start');
process.nextTick((a)= > {
  console.log('nextTick callback');
});
console.log('scheduled');
// start
// scheduled
// nextTick callback
Copy the code
process.nextTick()Can be used to control code execution order

Process.nexttick () can be used to give the user the ability to ensure that the method is called after the object completes constructor but before I/O occurs.

function MyThing(options) {
  this.setupOptions(options);
  process.nextTick((a)= > {
    this.startDoingStuff();
  });
}
const thing = new MyThing();
thing.getReadyForStuff(); // thing.startdoingStuff () is called after it is ready, not after initialization
Copy the code
This is important when the API is either 100% synchronous or 100% asynchronous

It is important that the API be either 100% synchronous or 100% asynchronous, and this guarantee can be achieved by making an API fully asynchronous through process.nexttick ().

// Can be synchronous, can be asynchronous API
function maybeSync(arg, cb) {
  if (arg) {
    cb();
    return;
  }
  fs.stat('file', cb);
}
Copy the code
// maybeTrue maybe false and maybeTrue, so the execution order of foo() and bar() is not guaranteed.
const maybeTrue = Math.random() > 0.5;
maybeSync(maybeTrue, () => {
  foo();
});
bar();
Copy the code

How to make the API completely an Async API? Or how to ensure that foo() is called after bar()? Complete asynchrony via process.nexttick ().

// A completely asynchronous API
function definitelyAsync(arg, cb) {
  if (arg) {
    process.nextTick(cb);
    return;
  }
  fs.stat('file', cb);
}
Copy the code

How to understand process.nexttick ()

You may notice that process.nexttick () does not appear in your code, even though it is part of the asynchronous API. Why is that? Because process.nexttick () is not a technical part of the Event loop. Instead, nextTickQueue is executed after the current operation completes, regardless of the current stage of the Event loop. Here, operation is defined as the transformation from the underlying C/C++ handler to the JavaScript that needs to be executed.

Looking back at our program, any phase in which you call process.nexttick (), any callback passed to process.nexttick () will be parsed before the event loop continues. This creates some bad situations by setting up a recursive process.nexttick () call, which allows you to “starve” your I/O. This keeps the Event loop from reaching the poll stage.

Why is process.Nexttick () a more powerful asynchronous expert?

Process.nexttick () delays calls more accurately than setTimeout()

Process.nexttick () delays calls more accurately than setTimeout(). Don’t worry, with doubt to see below. You can find the answer if you understand it.

Why does Node.js design this recursive process.nexttick ()? This is because part of node.js’s design philosophy is that the API must be async, even if it doesn’t have to be. Look at the following example:

function apiCall(arg, callback) {
    if(typeofarg ! = ='string') {return process.nextTick(callback, new TypeError('argument should be string')); }}Copy the code

The code snippet checks for argument and passes an error to the callback if it is not a string. The API was recently updated to allow parameters to be passed to process.nexttick (), which allows any parameters passed after callback to be passed as parameters to the callback, eliminating the need for nested functions.

What we are doing now is passing an error to the user, but only after the code we are allowed to execute has been executed. By using process.nexttick (), we can ensure that apiCall always runs its callback before the rest of the user code and allows the event loop to continue. To do this, the JS Call stack can be expanded, and then Immediately performs the provided callback, allowing a person to recursively call process.nexttick () without throwing a RangeError: Maximum call stack size exceeded from v8.

In a nutshell, process.nexttick () guarantees that the code we’re executing will execute properly before throwing this error. This is something that setTimeout() cannot do because we don’t know how long it will take to execute the code.

How does process.nexttick (callback) delay calls more strictly than setTimeout()? NextTick (callback) ensures that the callback will be called after the call stack is unwound from this event loop and before the next event loop.

Can you explain the reason in more detail?

Process.nexttick () will call the callback after this event loop’s call stack is cleared (before the next event loop starts). SetTimeout () does not know when the call stack is empty. SetTimeout (cb, 1000). After 1s, there are still several uncalled functions in the call stack, and it is not appropriate to set it to 10 seconds, because it may be finished in 1.1 seconds.

Process.nexttick () is very powerful. “Finally, I don’t need to adjust the setTimeout delay parameter!”

The powerful process.nexttick () solves real problems

This philosophy leads to some potential problems. Take a look at this code:

let bar;
// It is asynchronous, but calls callback synchronously
function someAsyncApiCall(callback) { callback(); }
// Callback is called before someAsyncApiCall completes
someAsyncApiCall((a)= > {
  // Because someAsyncApiCall has not been completed, bar has not been assigned
  console.log('bar', bar); // undefined
});
bar = 1;
Copy the code

The user defines someAsyncApiCall() with an asynchronous signature, but it actually executes synchronously. When someAsyncApiCall() is called, the internal callback is called before the asynchronous operation is complete. The callback tries to get a reference to bar, but the variable is not in scope because script has not yet executed to bar = 1.

Is there any way to ensure that this function is called after assignment?

By passing the callback to process.nexttick (), the script executes successfully, has access to all variables, functions, and so on, and is initialized before the callback is called. It has the advantage of not allowing the event loop to continue. It is useful for the user to alert an error before the Event loop wants to continue.

Here is the code above improved with process.nexttick () :

let bar;
function someAsyncApiCall(callback) {
    process.nextTick(callback);
}
someAsyncApiCall((a)= > {
  console.log('bar', bar); / / 1
});
bar = 1;
Copy the code

Here’s another real-world example:

const server = net.createServer((a)= > {}).listen(8080);
server.on('listening', () = > {});Copy the code

When we pass in a port number, the port number is immediately bound. So ‘listening’ callback can be called immediately. The problem is. On (‘ listening ‘); The callback may not be set yet. How do you do that?

In order to accurately listen for the actions of listen, the listening events are queued to nextTick(), allowing the code to run completely. This allows the user to set any event they want.

Why process.nexttick ()?

  • Allows the user to handle errors, clean up unwanted resources, or try the request again before the event loop
  • Sometimes make sure that the callback is called after calling the stack unwound and before the Event loop resumes

Allows the user to handle errors, clean up unwanted resources, or try the request again before the event loop

Here is an example of matching user expectations.

const server = net.createServer();
server.on('connection', (conn) => { });

server.listen(8080);
server.on('listening', () => {});Copy the code

Listen () runs at the beginning of the Event.loop, but the Listening callback is placed in setImmediate(). Bind the port immediately unless hostname is passed in. When the Event loop is processed, it must be in the poll phase, which means there is no chance of receiving a connection, allowing the Connection event to be fired before the listener event.

Sometimes make sure that the callback is called after calling the stack unwound and before the Event loop resumes

Here’s another example: Running a function Constructor that inherits EventEmitter and wants to emit an ‘event’ within its constructor.

const EventEmitter = require('events');
const util = require('util');

function MyEmitter() {
  EventEmitter.call(this);
  this.emit('event');
}
util.inherits(MyEmitter, EventEmitter);
const myEmitter = new MyEmitter();
myEmitter.on('event', () = > {console.log('an event occurred! '); // nothing happens
});
Copy the code

Emit an event cannot be understood within constructor because the script does not run to the point where the user listens for the event to respond to the callback. So inside Constructor, you can use process.nextTick to set up a callback to emit the event when constructor is done, so the final code looks like this:

const EventEmitter = require('events');
const util = require('util');

function MyEmitter() {
  EventEmitter.call(this);
  // Once the handler handler is assigned, issue the event using process.nexttick ()
  process.nextTick((a)= > {
    this.emit('event');
  });
}
util.inherits(MyEmitter, EventEmitter);
const myEmitter = new MyEmitter();
myEmitter.on('event', () = > {console.log('an event occurred! '); // an event occurred! '
});
Copy the code

Review the

Take a look back at process.nexttick () in the message Event source that MQtt.js uses to receive messages.

Process.nexttick () ensures that the work function is called exactly after this call stack is cleared and before the next event loop starts.

writable._write = function (buf, enc, done) {
    completeParse = done
    parser.parse(buf)
    work() / / start nextTick
}
function work () {
  var packet = packets.shift()
  if (packet) {
    that._handlePacket(packet, nextTickWork) // Notice here
  } else {
    // Abort the recursion of process.nexttick ()
    var done = completeParse
    completeParse = null
    if (done) done()
  }
}
function nextTickWork () {
  if (packets.length) {
    process.nextTick(work) // Notice here
  } else {
   // Abort the recursion of process.nexttick ()
    var done = completeParse
    completeParse = null
    done()
  }
}
Copy the code

After learning about process.nexttick () and understanding the source code, we conclude that the stream writes to work() locally, and if it receives a valid packet, process.nexttick () recurses.

  • Conditions for starting nextTick: if(packet)/if (packets.length) That is, the nextTick starts when the WebSocket packet is received.
  • The procedure for recursing nextTick: work()->nextTickWork()-> process.nexttick (work).
  • The condition for terminating nextTick is: Packet is empty or packets is empty. CompleteParse =null, done() terminates the recursion.
  • What happens if you don’t add process.nextTick to work?
function nextTickWork () {
  if (packets.length) {
    work() // Notice here}}Copy the code

This causes the current Event loop to never terminate and remain blocked, creating an infinite loop. It is process.nexttick () that ensures that the work function is called exactly after the call stack is cleared and before the next event loop starts.

Reference links:

  • Nodejs.org/uk/docs/gui…
  • Nodejs.org/dist/latest…
  • Github.com/mqttjs/MQTT…
  • Github.com/FrankKai/Fr…

I am looking forward to communicating with you and making progress together. Welcome to join the technical discussion group I created which is closely related to front-end development:

  • SegmentFault technosphere :ES new specification syntax sugar
  • SegmentFault column: Be a good front-end engineer while you’re still young
  • Zhihu column: Be an excellent front-end engineer while you are still young
  • Github blog: Personal blog 233 while You’re Still Young
  • Front-end development QQ group: 660634678
  • excellent_developers

Strive to be an excellent front-end engineer!