First let me explain how the code works - see the comments in the code that I added:
// first you define function A and nothing happens:
function A(callback) {
console.log('A');
callback();
}
// then you define function B and nothing happens:
function B() {
console.log('B')
}
// then you define function C and nothing happens:
function C() {
console.log('C');
}
// now you call function A with argument C:
A(C);
// when A runs it prints 'A' and calls C before it returns
// now the C runs, prints C and returns - back to A
// A now has nothing more to do and returns
// Now the execution continues and B can be run:
B();
// B runs and prints 'B'
This is exactly the same as it would be in any language like Java, C etc.
Now, the second example:
// first you define function A and nothing happens:
function A(callback) {
console.log('A');
process.nextTick(() => {
callback();
});
}
// then you define function B and nothing happens:
function B() {
console.log('B')
}
// then you define function C and nothing happens:
function C() {
console.log('C');
}
// Then you run A with C passed as an argument:
A(C);
// A prints 'A' and schedules running an anonymous function:
// () => { callback(); }
// on the next tick of the event loop, before I/O events are handled
// but after the current code stack is unrolled
// then it returns
// And then B is run:
B();
// B prints 'B' and returns
// Now nothing else is left to do so the next tick of the event loop starts
// There's one function to run, scheduled earlier so it runs.
// This function runs the `callback()` which was `C`
// so C starts, prints 'C' and returns
// The anonymous function has nothing else to do and returns
// There is no more things on the event loop so the program exits
Update
Thanks to Bergi for explaining what is Zalgo in his answer. Now I better understand your concerns.
Is this what we call as zalgo ? Can anyone provide me a realtime example of this, which will cause major breakdown ?
I've seen a lot of code like this:
function x(argument, callback) {
if (!argument) {
return callback(new Error('Bad argument'));
}
runSomeAsyncFunction(argument, (error, result) => {
if (error) {
return callback(new Error('Error in async function'));
}
callback({data: result});
});
}
Now, the callback can be either run immediately before x()
returns if there are bad arguments, or after the x()
returns otherwise. This code is very common. For testing the arguments one could argue that it should throw an exception but let's ignore that for a moment, there may be some better examples of operational errors that are known immediately - this is just a simple example.
Now, if it was written like this:
function x(argument, callback) {
if (!argument) {
return process.nextTick(callback, new Error('Bad argument'));
}
runSomeAsyncFunction(argument, (error, result) => {
if (error) {
return callback(new Error('Error in async function'));
}
callback({data: result});
});
}
it would be guaranteed that the callback will never be invoked before x()
returns.
Now, if that can cause a "major breakdown" depends entirely on how it is used. If you run something like this:
let a;
x('arg', (err, result) => {
// assume that 'a' has a value
console.log(a.value);
});
// assign a value to 'a' here:
a = {value: 10};
then it will sometimes crash with the version of x()
without process.nextTick
and will never crash with the version of x()
with process.nextTick()
.