Node.js: Asynchronous Callback Execution. Is this

2019-01-29 13:57发布

Executing the below using node - 6.0.

function A(callback) {
  console.log('A');
  callback();
}

function B() {
  console.log('B')
}

function C() {
  console.log('C');
}

A(C);
B();


// Result is A,C,B i expected that A, B, C

But changing the above example to use process.nextTick() prints A, B, C

function A(callback) {
  console.log('A');
  process.nextTick(() => {
    callback();
  });
}

function B() {
  console.log('B')
}

function C() {
  console.log('C');
}

A(C);
B();

Is this what we call as zalgo ? Can anyone provide me a realtime example of this, which will cause major breakdown ?

2条回答
孤傲高冷的网名
2楼-- · 2019-01-29 14:25

No, neither of these is zalgo. Your first A function always calls its callback synchronously, and should be documented as such. Your second A function always calls its callback asynchronously, and should be documented as such. Nothing is wrong with that, we use thousands of these every day. The outputs A C B and A B C are deterministic.

Zalgo refers to the uncertainty whether a callback is asynchronous or not.

function A(callback) {
  console.log('A');
  if (Math.random() < 0.5) {
    callback();
  } else {
    process.nextTick(callback);
  }
}

The output of the invocation A(C); B(); would be totally unpredictable.

查看更多
Fickle 薄情
3楼-- · 2019-01-29 14:33

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().

查看更多
登录 后发表回答