Downside to not wrapping sync callbacks in nextTick()?

1,203 views
Skip to first unread message

Bryan Donovan

unread,
Aug 20, 2013, 1:47:22 PM8/20/13
to nod...@googlegroups.com
I have been writing node.js client code for a couple of years now, and have authored a couple open source libraries, but somehow I missed the memo telling me that I'm supposed to wrap 'synchrounous' callbacks in process.nextTick().  I kind-of understand why that is a best-practice, but what I don't understand is what the drawback is if you don't do it.

For example, I write code like this all the time, and have never had a single problem with it:

function getSomething(args, cb) {
    if (!args) { return cb(new Error('args required')); }
    if (!args.id) { return cb(new Error('args.id required')); }

    SomeDatabase.get({id: args.id}, cb);
}

What are the potential issues with not wrapping those arg checks in process.nextTick()?


Thanks, 

Bryan

Scott González

unread,
Aug 20, 2013, 1:58:47 PM8/20/13
to nod...@googlegroups.com
The potential issue is if the person consuming your API expects the callback to always be async:

// kick off the async process as early as possible
doAsync( cb );
// do some slightly high cost prep work before the callback is invoked
prepareStateForCallbackWhileWaiting();

Doing the prep work after the async call allows you to reduce the amount of time needed between the start of this tick and the tick when the callback is actually invoked. Interestingly, the reason some people don't like forcing async is for performance gains when the operation can be sync.

You're going to get lots of arguments on both sides.



--
--
Job Board: http://jobs.nodejs.org/
Posting guidelines: https://github.com/joyent/node/wiki/Mailing-List-Posting-Guidelines
You received this message because you are subscribed to the Google
Groups "nodejs" group.
To post to this group, send email to nod...@googlegroups.com
To unsubscribe from this group, send email to
nodejs+un...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/nodejs?hl=en?hl=en
 
---
You received this message because you are subscribed to the Google Groups "nodejs" group.
To unsubscribe from this group and stop receiving emails from it, send an email to nodejs+un...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Aria Stewart

unread,
Aug 20, 2013, 2:00:44 PM8/20/13
to nod...@googlegroups.com, Bryan Donovan
On 20 August 2013 at 1:47:45 PM, Bryan Donovan (brdo...@gmail.com) wrote:
I have been writing node.js client code for a couple of years now, and have authored a couple open source libraries, but somehow I missed the memo telling me that I'm supposed to wrap 'synchrounous' callbacks in process.nextTick().  I kind-of understand why that is a best-practice, but what I don't understand is what the drawback is if you don't do it.

For example, I write code like this all the time, and have never had a single problem with it:

function getSomething(args, cb) {
    if (!args) { return cb(new Error('args required')); }
    if (!args.id) { return cb(new Error('args.id required')); }

    SomeDatabase.get({id: args.id}, cb);
}

It means that things happen in a different order depending on cases — for errors, this is not as likely a problem, but in, say, a caching callback, if the cached values are immediate, and the non-cached values spin the event loop, you have some significantly different behavior depending on which case you're in -- in one case, you have a full stack still, after the event loop spins, you've cleared the stack (and exception handlers).



Bryan Donovan

unread,
Aug 20, 2013, 2:12:35 PM8/20/13
to nod...@googlegroups.com
Thanks Scott.

That's basically what I had gathered from googling around.  It seems to me like it's not necessary to wrap in nextTick for most cases then.


You received this message because you are subscribed to a topic in the Google Groups "nodejs" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/nodejs/0TmVfX9z1R0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to nodejs+un...@googlegroups.com.

Bryan Donovan

unread,
Aug 20, 2013, 3:16:59 PM8/20/13
to nod...@googlegroups.com
Another issue with using process.nextTick() is it ruins the stack trace (which might be fine or maybe even desirable in some cases, but isn't so great for error checking/handling).  That can be fixed by using bind(), but at that point we're adding a lot of cruft to the code IMO. (Related gist that shows stack traces: https://gist.github.com/BryanDonovan/6254656).

As a real-life example, I wrote an Elasticsearch client that checks for missing args in fashion I mentioned earlier in this thread (e.g., https://github.com/BryanDonovan/node-simple-elasticsearch/blob/master/lib/client.js#L94).  Is that something I need to change?

Thanks,

Bryan


Floby

unread,
Aug 21, 2013, 3:32:55 AM8/21/13
to nod...@googlegroups.com
Same as above.
A "callback" is meant to be called back after some operations are made. In node, that means they're probably gonna get called when the current stack has unwound. If you called the callback synchronously, that means it behaves differently.
process.nextTick un most cases execute the given function immediately after the current stack ends (with some limitations for recursivity) so it's not really a performance killer.

Doing so helps you respect your function/method contract :)

Saying that "in most case, it's not needed" is too big of an assumption of what your user is doing with your code. IMO.

Bryan Donovan

unread,
Aug 21, 2013, 4:19:09 AM8/21/13
to nod...@googlegroups.com
Thanks for the reply.

However, this is the same answer as what I keep coming across that doesn't really answer my question.  What is the downside?  I want to see a real-world example of what happens when I don't follow this contract. I've yet to see an example of something bad happening in this situation.  I'm sure I'm wrong, but I need to know *why*.

When would someone ever call:

   getSomething(args, cb);

... and then assume they can call something else in the meantime?


Thanks,

Bryan


Scott González

unread,
Aug 21, 2013, 8:33:09 AM8/21/13
to nod...@googlegroups.com
You received this message because you are subscribed to the Google Groups "nodejs" group.
To unsubscribe from this group and stop receiving emails from it, send an email to nodejs+un...@googlegroups.com.

Bryan Donovan

unread,
Aug 21, 2013, 2:46:18 PM8/21/13
to nod...@googlegroups.com
Thanks Scott, that thread was very helpful.

I think I get it now, but it does seem to me that executing the callback synchronously in the particular ways I've been doing it is ok.  If I were following a convention like:

function connect(cb) {
    process.nextTick(cb);

    return {
        foo: function() {
            console.log('foo!');
        }
    };
}

function foo() {
    var client = connect(function() {
        client.foo();
    });
}

foo();

.. then I totally see why we need to use nextTick there.

But I guess I'll change my public libraries to use nextTick() just in case someone uses them in a strange way.


Thanks again,

Bryan

Oleg Slobodskoi

unread,
Aug 22, 2013, 9:11:50 AM8/22/13
to nod...@googlegroups.com
Here is another real world example:

var todo = 0;

function done() {
    todo--;

    if (!todo) callback();
}

todo++;

// If "a" calls back synchronously, todo amount will never be "2",  so "done" will call back directly after a calls back.
a(done);

if (soemthing) {
    todo++;
    b(done);
}




You received this message because you are subscribed to the Google Groups "nodejs" group.
To unsubscribe from this group and stop receiving emails from it, send an email to nodejs+un...@googlegroups.com.

Eldar

unread,
Aug 22, 2013, 12:44:26 PM8/22/13
to nod...@googlegroups.com
I always followed the simple rule "callback may be called at any time" and I was pretty happy so far.
The resulting code is more portable and simpler. Just use tail recursive algorithms and be happy.

вторник, 20 августа 2013 г., 21:47:22 UTC+4 пользователь Bryan Donovan написал:

Scott González

unread,
Aug 22, 2013, 12:49:46 PM8/22/13
to nod...@googlegroups.com
This is just the robustness principle/Postel's law: Be conservative in what you send, be liberal in what you accept.

If you're implementing an API, you should be consistent. If you're consuming the API, you should be defensive. But honestly, people writing node modules should follow the node patterns and never mix sync and async behavior.


--

Bryan Donovan

unread,
Aug 22, 2013, 2:08:22 PM8/22/13
to nod...@googlegroups.com
Cool.  I've updated node-simple-elasticsearch to call back with errors asynchronously: https://github.com/BryanDonovan/node-simple-elasticsearch/blob/master/lib/validator.js

(at least I think I did it right).

Bryan


You received this message because you are subscribed to a topic in the Google Groups "nodejs" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/nodejs/0TmVfX9z1R0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to nodejs+un...@googlegroups.com.

Eldar

unread,
Aug 22, 2013, 2:22:43 PM8/22/13
to nod...@googlegroups.com
> Be conservative in what you send, be liberal in what you accept.

This is absolutely wrong! You should be conservative in what you accept and conservative
in what you send otherwise you get huge troubles, shits and endless "it works here doesn't there" story.
Browsers are good example.

>people writing node modules should follow the node patterns and never mix sync and async behavior

There is a certain edge between the case when you should follow common practices event if you don't like them
and the case when you shouldn't, because they are so bad that cause a greater damage
than confusing people from time to time.

For the case of nextTick I ignored it, because it's such a PITA.

четверг, 22 августа 2013 г., 20:49:46 UTC+4 пользователь Scott González написал:

Scott González

unread,
Aug 22, 2013, 2:22:59 PM8/22/13
to nod...@googlegroups.com
The success case is synchronous. It looks like there's nothing that ever needs to be async in that function. Why does this take a callback instead of just returning a boolean?

Bryan Donovan

unread,
Aug 22, 2013, 2:30:32 PM8/22/13
to nod...@googlegroups.com
Because the calling code calls back with the error (e.g., https://github.com/BryanDonovan/node-simple-elasticsearch/blob/master/lib/client.js#L54), and it was easier to add the nextTick() stuff inside the validator function instead of in each caller.  I previously had it purely synchronous.. but changed it because of this thread.  That validator.js file is not exported in the npm.. it's private.


-Bryan

Scott González

unread,
Aug 22, 2013, 2:32:28 PM8/22/13
to nod...@googlegroups.com
On Thu, Aug 22, 2013 at 2:22 PM, Eldar <elda...@gmail.com> wrote:
> Be conservative in what you send, be liberal in what you accept.

This is absolutely wrong! You should be conservative in what you accept and conservative
in what you send otherwise you get huge troubles, shits and endless "it works here doesn't there" story.
Browsers are good example.

This is so misguided it's not even funny. There's a huge difference between being liberal and having defined results for every input and the bullshit that went on during the major browser wars.

>people writing node modules should follow the node patterns and never mix sync and async behavior

There is a certain edge between the case when you should follow common practices event if you don't like them
and the case when you shouldn't, because they are so bad that cause a greater damage
than confusing people from time to time.

You realize that if you want to be conservative as an API developer, you need to use nextTick(), right? There's absolutely zero change of danger or confusing when forcing async callbacks. All of the danger and confusion comes from sometimes-sync callbacks.
 
For the case of nextTick I ignored it, because it's such a PITA.

Sorry that writing one more line of code is so troublesome for you.

Scott González

unread,
Aug 22, 2013, 2:36:43 PM8/22/13
to nod...@googlegroups.com
To be honest, it seems like laziness over correctness. You now can't have a method that just validates synchronously. You also now need to trace the code to prove the correctness of the code, since you need to ensure that any use of the validation will result in an async callback at some point. Or you can put the success case inside a nextTick() as well, but then you're taking an unnecessary hit everywhere, not just on error.

Bryan Donovan

unread,
Aug 22, 2013, 2:45:36 PM8/22/13
to nod...@googlegroups.com
Yeah, I agree.  I originally did it this way because I erroneously thought I'd have to do some kind of cb.bind() trickery to preserve stack traces, and that was too much cruft in the main client code.

Eldar

unread,
Aug 22, 2013, 4:02:36 PM8/22/13
to nod...@googlegroups.com
I am sorry, I had to write more detailed explanation of my above statements.

Postel low (Robustness principle)

You can have a look at wikipedia article about it http://en.wikipedia.org/wiki/Robustness_principle.
It contains explanation about why the robustness principle is harmful and contains a link to the corresponding RFC.

About browsers. I am not much aware history of browsers wars, but I am aware about
HTML parsers and what a shit they are and heard about how much efforts it took to make
all that quirk stuff be a standard.  Although browsers had a greater excuse for that than a pure machine protocols.

Finally, I personally suffered from "robustness" even within my *private* tool chain :)

process.nextTick

Downsides of process.nextTick are

1) It is none-standard, not available in other environments and even can't be shimed (with promise of the same performance).
2) It has performance costs. Significant or not depends on your use case.
3) It disables some use cases.

Example for 2 - 3

Let's say you have a some streaming app which receives something, transforms it and sends results further.

TCP -> PARSER -> TRANSFORM -> SEND
  
No surprise that PARSER often receives a chunk which contains many protocol objects.
So, deferring them with nextTick is a performance overhead. And indeed, for example
cursor.nextObject() of node-mongodb-native calls it's callback synchronously exactly for the same reason.
On other hand SEND also may decide to pack several protocol objects in one network packet. One possible
and sometimes best way to do that is to consume what is available right now in the same tick, pack that and send.
This is not possible when objects are deferred.

About 1) and that it's none-standard I can add that when nextTick semantics were changed in 0.10.x and temporal max tick depth limitation
were added many module authors were confused. They haven't discovered process.maxTickDepth setting and patched their code
to use new setImmediate() function instead. Loosing performance of new process.nextTick and adding a junk to their code.

Hope, now my claims look more sane and making sens.


четверг, 22 августа 2013 г., 22:32:28 UTC+4 пользователь Scott González написал:

Eldar

unread,
Aug 22, 2013, 4:12:31 PM8/22/13
to nod...@googlegroups.com
Also about process.nextTick "standard". You might be surprised, but I very rarely see sync callbacks deferring in modules which our app uses. directly or not.

пятница, 23 августа 2013 г., 0:02:36 UTC+4 пользователь Eldar написал:

Scott González

unread,
Aug 22, 2013, 4:22:21 PM8/22/13
to nod...@googlegroups.com
On Thu, Aug 22, 2013 at 4:02 PM, Eldar <elda...@gmail.com> wrote:
I am sorry, I had to write more detailed explanation of my above statements.

Postel low (Robustness principle)

You can have a look at wikipedia article about it http://en.wikipedia.org/wiki/Robustness_principle.
It contains explanation about why the robustness principle is harmful and contains a link to the corresponding RFC.

About browsers. I am not much aware history of browsers wars, but I am aware about
HTML parsers and what a shit they are and heard about how much efforts it took to make
all that quirk stuff be a standard.  Although browsers had a greater excuse for that than a pure machine protocols.

Finally, I personally suffered from "robustness" even within my *private* tool chain :)

I'm sorry, but again, this is misguided. The complaint in that article is that by being liberal, you're masking potential problems in the calling code. The only potential problem comes from other API developers begin inconsistent and allowing sometimes-sync callbacks. Whether you want to believe it or not, forcing always sync is both the strict and liberal implementation. It's strict because it guarantees a specific order. It's liberal because it guarantees the caller can't write broken code. So perhaps this isn't the robustness principle exactly, but the fact remains that the safest thing for everyone is always async.

process.nextTick

Downsides of process.nextTick are

1) It is none-standard, not available in other environments and even can't be shimed (with promise of the same performance).

We're talking about node. If you're writing a multi-environment module, then all node standards are out. The standard already exists directly in node core. The core team has publicly stated that you should force always async in a thread I already linked to.

2) It has performance costs. Significant or not depends on your use case.

It's quite rare for modules to actually have significant performance costs based on forced async. It does happen, but very rarely.
 
3) It disables some use cases.

This cannot be true. You cannot have a sometimes-async API which breaks when not always async.

Adam Crabtree

unread,
Aug 22, 2013, 4:31:32 PM8/22/13
to nod...@googlegroups.com
FWIW, the error issue is a strawman. You can/should use long-stack-traces for debugging/development, and trycatch (https://github.com/CrabDude/trycatch/) for production, which avoids the need to both restart on domain errors (http://nodejs.org/docs/latest/api/all.html#all_warning_don_t_ignore_errors) and the need to use domain.bind.

Cheers,
Adam Crabtree


--
--
Job Board: http://jobs.nodejs.org/
Posting guidelines: https://github.com/joyent/node/wiki/Mailing-List-Posting-Guidelines
You received this message because you are subscribed to the Google
Groups "nodejs" group.
To post to this group, send email to nod...@googlegroups.com
To unsubscribe from this group, send email to
nodejs+un...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/nodejs?hl=en?hl=en
 
---
You received this message because you are subscribed to the Google Groups "nodejs" group.
To unsubscribe from this group and stop receiving emails from it, send an email to nodejs+un...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.



--
Better a little with righteousness
       than much gain with injustice.
Proverbs 16:8

Oleg Slobodskoi

unread,
Aug 23, 2013, 5:32:11 AM8/23/13
to nod...@googlegroups.com
A bit out of topic, as we are all agreed async api should be always async, one can use setImmediate, because it will perfectly fit our needs, because it supports arguments (vs. nextTick) and doesn't requires delay in ms (setTimeout).

So the writing overhead by using it is pretty minimal:


if (err) {
    return callback(err);
}

vs.

if (err) {
    return setImmediate(callback, err);

Chaoran Yang

unread,
Aug 23, 2013, 10:32:29 AM8/23/13
to nod...@googlegroups.com
But setImmediate has a much higher performance overhead than nextTick.

Chaoran Yang

unread,
Aug 23, 2013, 10:53:40 AM8/23/13
to nod...@googlegroups.com
I think using nextTick() for error handling is a very very bad idea. 

Stack trace could be very helpful when error happens. use nextTick basically throws that stack trace ability away.

Moreover, the whole nextTick thing is stupid IMO. When I use an asynchronous API, I always assume it *may* be optimized for some cases to run synchronously. So changing the state of a callback function between it is used and it is called is a very very bad idea. In almost every case I ran into, it turned out to be a programmer mistake rather than an intended behavior. The programmer should *NOT* assume a guaranteed execution order. It is a very unnecessary overhead (sometimes also very large) to pay for API developers just to guarantee execution order, because execution order is irrelevant in most cases.

To sum up, 
1) Programmer should assume an asynchronous API may callback synchronously because of optimization for some fast path.
2) Guarantee execution order of a callback is unnecessary in most cases, so it's an overhead that should be avoided by default.
3) Definitely DO NOT use nextTick for error handling code, because it throws away the stack information for nothing.

-Chaoran

Oleg Slobodskoi

unread,
Aug 23, 2013, 11:21:37 AM8/23/13
to nod...@googlegroups.com
This all are good arguments, in the theory ... in praxis, I think:

1. Most developers do not assume async/sync mixed styles today ...
2. That kind of performance overhead matters in some rare cases, I think ... normally you don't care about 10-15ms ... If the overhead is much higher its about the overall process load ... even if you get your callback faster, some other parts of the application will wait. At the end the whole application will perform at the same speed ... no?
3. This is one of nodejs very bad parts which should be possibly solved on some other level, saving stacks over X loops ... or whatever .... All the longstacktraces modules I have tried have consumed huge amount of memory so its not for production.

Oleg


Chaoran Yang

unread,
Aug 23, 2013, 11:59:09 AM8/23/13
to nod...@googlegroups.com, ole...@googlemail.com


On Friday, August 23, 2013 10:21:37 AM UTC-5, Oleg Slobodskoi wrote:
This all are good arguments, in the theory ... in praxis, I think:

1. Most developers do not assume async/sync mixed styles today ...

In practice, coding standard should not be lower because of programmer laziness, right?
 
2. That kind of performance overhead matters in some rare cases, I think ... normally you don't care about 10-15ms ... If the overhead is much higher its about the overall process load ... even if you get your callback faster, some other parts of the application will wait. At the end the whole application will perform at the same speed ... no?

Yes, the overhead matters only in rare cases. But imagine you're writing an API that unfortunately falls into that rare case, what would you do? Do you use nextTick to pay that unnecessary and large overhead, or do you use invoke callback synchronously and add a "CAUTION" in your doc to warn programmers? Both ways are too inelegant IMO. I think the best way to go would be educating programmers to assume all asynchronous calls may be executed synchronously, that way nobody will need to pay the unnecessary overhead (it's a big deal for whose who fall into that rare cases!), and no need to warn your user in the doc.
 
3. This is one of nodejs very bad parts which should be possibly solved on some other level, saving stacks over X loops ... or whatever .... All the longstacktraces modules I have tried have consumed huge amount of memory so its not for production.

I don't think it's nodejs's bad. It's bad programming principle. Throw away your stack information just to guarantee execution order when the user in most cases might not need at all? Seriously? 

IMO It can be solved very easily. Just don't use nextTick for error handling code. Period.

Oleg Slobodskoi

unread,
Aug 23, 2013, 12:11:13 PM8/23/13
to nod...@googlegroups.com



On Friday, August 23, 2013 10:21:37 AM UTC-5, Oleg Slobodskoi wrote:
This all are good arguments, in the theory ... in praxis, I think:

1. Most developers do not assume async/sync mixed styles today ...

In practice, coding standard should not be lower because of programmer laziness, right?

Depends on how much pain it causes ... If official nodejs doc warns people then its probably fine ...


 
2. That kind of performance overhead matters in some rare cases, I think ... normally you don't care about 10-15ms ... If the overhead is much higher its about the overall process load ... even if you get your callback faster, some other parts of the application will wait. At the end the whole application will perform at the same speed ... no?

Yes, the overhead matters only in rare cases. But imagine you're writing an API that unfortunately falls into that rare case, what would you do? Do you use nextTick to pay that unnecessary and large overhead, or do you use invoke callback synchronously and add a "CAUTION" in your doc to warn programmers? Both ways are too inelegant IMO. I think the best way to go would be educating programmers to assume all asynchronous calls may be executed synchronously, that way nobody will need to pay the unnecessary overhead (it's a big deal for whose who fall into that rare cases!), and no need to warn your user in the doc.

I have never seen such cases ... and can't imagine them ... but potentially yes ...

 
3. This is one of nodejs very bad parts which should be possibly solved on some other level, saving stacks over X loops ... or whatever .... All the longstacktraces modules I have tried have consumed huge amount of memory so its not for production.

I don't think it's nodejs's bad. It's bad programming principle. Throw away your stack information just to guarantee execution order when the user in most cases might not need at all? Seriously? 

IMO It can be solved very easily. Just don't use nextTick for error handling code. Period.


stack trace problem is there independent of the topic ... you are using lots of real async apis which WILL drop your stack trace ....

Bryan Donovan

unread,
Aug 23, 2013, 12:17:21 PM8/23/13
to nod...@googlegroups.com
The stack trace is only ruined if the error is created inside the nextTick() block.  See:


--
--
Job Board: http://jobs.nodejs.org/
Posting guidelines: https://github.com/joyent/node/wiki/Mailing-List-Posting-Guidelines
You received this message because you are subscribed to the Google
Groups "nodejs" group.
To post to this group, send email to nod...@googlegroups.com
To unsubscribe from this group, send email to
nodejs+un...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/nodejs?hl=en?hl=en
 
---
You received this message because you are subscribed to a topic in the Google Groups "nodejs" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/nodejs/0TmVfX9z1R0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to nodejs+un...@googlegroups.com.

Bryan Donovan

unread,
Aug 23, 2013, 12:25:14 PM8/23/13
to nod...@googlegroups.com
This is certainly only way I've ever programmed in node.js. I really don't like APIs like this:

    var client = connect(function() {
        client.do_something(function() {
            //..
        });
    });

… they're just weird.  This makes a lot more sense to me:

    connect(function(err, client) {
        client.do_something(function() {
            //..
        });
    });

I know the first way might be necessary in some cases, but I avoid it if possible.

Chaoran Yang

unread,
Aug 23, 2013, 1:39:11 PM8/23/13
to nod...@googlegroups.com
Totally agree.

But I think the first case can be completed avoided. If the function can return a variable, it can definitely pass that variable as an argument to the callback function.

-Chaoran

Chaoran Yang

unread,
Aug 23, 2013, 1:46:27 PM8/23/13
to nod...@googlegroups.com, ole...@googlemail.com
Depends on how much pain it causes ... If official nodejs doc warns people then its probably fine ... 

I just found out official nodejs doc has that in there, which is a very bad idea I think. 

I have never seen such cases ... and can't imagine them ... but potentially yes ...

The cursor.nextObject() of node-mongodb-native that @Eldar has brought up is a pretty good one.

stack trace problem is there independent of the topic ... you are using lots of real async apis which WILL drop your stack trace ....

Okay, I agree with this one.
 
-Chaoran

Scott González

unread,
Aug 23, 2013, 1:58:03 PM8/23/13
to nod...@googlegroups.com
On Fri, Aug 23, 2013 at 1:46 PM, Chaoran Yang <chaora...@gmail.com> wrote:
I have never seen such cases ... and can't imagine them ... but potentially yes ...

The cursor.nextObject() of node-mongodb-native that @Eldar has brought up is a pretty good one.

Are there real world benchmarks showing that this is a legitimate performance bottleneck? 

Isaac Schlueter

unread,
Aug 28, 2013, 9:16:03 PM8/28/13
to nodejs

Bryan Donovan

unread,
Aug 29, 2013, 12:51:44 PM8/29/13
to nod...@googlegroups.com
Thanks for the post, Isaac.  Very helpful.

I totally understand the logic of being consistent -- I'm known to be ruthless about consistency at work.   I'm changing my public node.js libraries to use nextTick() instead of calling back immediately.

However, I'm still unsure of when someone would ever care if I called back immediately in my examples, where I'm just checking for the correct args.  An example use case:

var db = {
    get: function(args, cb) {
        args = args || {};
        if (!args.index) { return cb(new Error('index required')); }
        if (!args.type) { return cb(new Error('type required')); }

        doSomethingAsync(args, cb);
    }
};

function getSomething(args, cb) {
    db.get(args, cb);
}

// Express.js-style route 
app.get('/foo', function(req, res) {
    var args = {index: req.params.index, type: req.params.type, id: req.params.id};
    getSomething(args, function(err, result) {
        if (err) {
            res.send(500, JSON.stringify(err));
        } else {
            res.send(200, JSON.stringify(result));
        }
    });
});

This is how I'd expect people to use this client.  I must still be missing something, but *in this situation* I still don't see where someone would run into trouble due to the error callbacks being returned immediately.

Thanks,
Bryan

You received this message because you are subscribed to a topic in the Google Groups "nodejs" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/nodejs/0TmVfX9z1R0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to nodejs+un...@googlegroups.com.

Scott González

unread,
Aug 29, 2013, 12:57:20 PM8/29/13
to nod...@googlegroups.com
On Thu, Aug 29, 2013 at 12:51 PM, Bryan Donovan <brdo...@gmail.com> wrote:
However, I'm still unsure of when someone would ever care if I called back immediately in my examples, where I'm just checking for the correct args.

Oleg's generic example seemed pretty clear to me: https://groups.google.com/d/msg/nodejs/0TmVfX9z1R0/y46iAwFEJ0YJ 

Bryan Donovan

unread,
Aug 29, 2013, 1:50:22 PM8/29/13
to nod...@googlegroups.com
That's not the same situation though.  What bad can happen in my most recent example?

Scott González

unread,
Aug 29, 2013, 2:16:41 PM8/29/13
to nod...@googlegroups.com
Yes, it's possible to construct specific situations that don't have problems. Nobody is denying that. But it's irrelevant if it's also possible to construct situations that do have problems.


You received this message because you are subscribed to the Google Groups "nodejs" group.
To unsubscribe from this group and stop receiving emails from it, send an email to nodejs+un...@googlegroups.com.

Bryan Donovan

unread,
Aug 29, 2013, 2:19:41 PM8/29/13
to nod...@googlegroups.com
Thanks, I just wanted to make sure my understanding was correct in that there really is no problem with the specific case I presented.

Mark Hahn

unread,
Aug 29, 2013, 3:51:29 PM8/29/13
to nodejs
 But it's irrelevant if it's also possible to construct situations that do have problems.

That is illogical.  Code that is bulletproof is always OK.  You are confusing philosophy with practice.

Mark Hahn

unread,
Aug 29, 2013, 3:57:39 PM8/29/13
to nodejs
But if you are talking about providing a library to others then that is not bulletproof.  I was only thinking of my own code, which uses mixed sync and async all the time.

Isaac Schlueter

unread,
Aug 29, 2013, 10:04:33 PM8/29/13
to nodejs
On Thu, Aug 29, 2013 at 9:51 AM, Bryan Donovan <brdo...@gmail.com> wrote:
> I totally understand the logic of being consistent

You say this. And yet, you release Zalgo right away in your example!
Never ever do this!

> However, I'm still unsure of when someone would ever care if I called back
> immediately in my examples, where I'm just checking for the correct args.

Because by calling back immediately *sometimes*, and not immediately
other times, you're making your program (and by extension, any program
that passes control to it) impossible to reason about.

> An example ͝us͞e̶ ̴c̀aşe͞:
>
> var db = {
> get: function(args, cb) {
> args = args || {};
> if (!args.index) { return cb(new Error('ind̀e͡x͞ required')); }
> if (!args.type) { return cb(neẃ Er͞ro͏r̨(͘'typ͏e réqui̡red')); }
>
> d̴͘o̷S̵̡ó̢m̡҉́eth͏͟i̡͞ng̵As̸yńc(args, c̢͝b);
> ͡͏̡͜ }
> };

ZZAAAALLLLLGOOOOOO!

If you want to check args right away, and stop going any further, you
have two options that do not open portals to madness tainting
everything that they even threaten to touch:

1. nextTick the callback.
2. Throw right now.

// option 1
var db = {
get: function(args, cb) {
args = args || {};
if (!args.index) { next(new Error('index required')); }
if (!args.type) { next(new Error('type required')); }
function next(er) {
process.nextTick(function() {
cb(er)
});
}
doSomethingAsync(args, cb);
}
};

// option 2
var db = {
get: function(args, cb) {
args = args || {};
if (!args.index) { throw new Error('index required'); }
if (!args.type) { throw new Error('type required'); }
doSomethingAsync(args, cb);
}
};

Isaac Schlueter

unread,
Aug 29, 2013, 10:09:11 PM8/29/13
to nodejs
On Thu, Aug 29, 2013 at 11:19 AM, Bryan Donovan <brdo...@gmail.com> wrote:
> Thanks, I just wanted to make sure my understanding was correct in that
> there really is no problem with the specific case I presented.

That understanding is incorrect.

The exact problem being described in Havoc's post and in mine, is a
problem with the specific case you presented.

Throw now, or nextTick the callback. Don't do it half-way.

On Thu, Aug 29, 2013 at 7:04 PM, Isaac Schlueter <i...@izs.me> wrote:
> if (!args.index) { next(new Error('index required')); }
> if (!args.type) { next(new Error('type required')); }

Ugh, a bug in my example. It should be `return next(new Error(...))`,
so that it doesn't try to do the async thing if it's already called
the cb with an error.

Hage Yaapa

unread,
Aug 30, 2013, 1:04:50 AM8/30/13
to nod...@googlegroups.com
The bottom line is "APIs should be consistent in behavior".


Juraj Kirchheim

unread,
Aug 30, 2013, 5:24:57 AM8/30/13
to nod...@googlegroups.com
On Thu, Aug 29, 2013 at 3:16 AM, Isaac Schlueter <i...@izs.me> wrote:
> http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony

Nice article, although I think the whole first section is out of
place. You spend a whole page on explaining why your choice of
abstraction doesn't matter. And for the rest of the article you argue
only about problems that promises simply do not have. There's a reason
of introducing this "stuff" that's beyond provincial preference ;)

Scott González

unread,
Aug 30, 2013, 8:22:29 AM8/30/13
to nod...@googlegroups.com
That's a flat out lie. The concept of a promise does nothing to prevent this problem. Forced async promises on the other hand, do not suffer from this problem.

Juraj Kirchheim

unread,
Aug 30, 2013, 8:30:05 AM8/30/13
to nod...@googlegroups.com
Calm your tone. It's not a lie. It's just something that you seem not
to understand.
If you actually care for an explanation, I will be glad to give you one.
But if you're just into screaming "lie" at things that you cannot
grasp, then I will spare both of us a further waste of time.

Scott González

unread,
Aug 30, 2013, 8:42:01 AM8/30/13
to nod...@googlegroups.com
I fail to see where I screamed. I simply stated a fact (I've been bitten by this exact problem with promises before). Please, explain how promises solve this.

Dean Landolt

unread,
Aug 30, 2013, 9:08:57 AM8/30/13
to nod...@googlegroups.com
Many years ago I used to write functions that almost always returned promises, except when they didn't. And I thought it was great, and efficient. I just needed to "lift" the values of a possible promises into an actual promises using something like Q.when. This works wonderfully, until you hit an exception in one of your zalgo-promise functions. This occurs before the promise chain has a chance to wrap and catch, so you're pretty well screwed.

Which means you have to wrap pretty much any zalgo-promise, or use some goofy apply mechanism. Assuming you meticulously add all this boilerplate then yes, at this point Juraj is right. It "solves" the problem (with a nextTick or equivalent, of course). But at what cost?


--

Scott González

unread,
Aug 30, 2013, 9:19:46 AM8/30/13
to nod...@googlegroups.com
On Fri, Aug 30, 2013 at 9:08 AM, Dean Landolt <de...@deanlandolt.com> wrote:
Which means you have to wrap pretty much any zalgo-promise, or use some goofy apply mechanism. Assuming you meticulously add all this boilerplate then yes, at this point Juraj is right. It "solves" the problem (with a nextTick or equivalent, of course). But at what cost?

This sounds like the opposite of promises solving a problem, and exactly like what Isaac said about abstraction not mattering. As soon as you use nextTick() or equivalent, you've proven the point that being consistently async is what matters, not the abstraction over function invocation.

Dean Landolt

unread,
Aug 30, 2013, 10:15:23 AM8/30/13
to nod...@googlegroups.com
You say "this sounds like the opposite of promises solving a problem", which I assume would mean promises creating a problem? This obviously conflicts with the "abstraction not mattering" part. Everything I said was to reinforce the point that the abstraction doesn't matter and demonstrate how zalgo's still a footgun for promises. So I was generally agreeing with you, except with the part where you called Juraj's statement a flat out lie -- it was more of an underestimation of the zalgo on Juraj's part :)


--

Bryan Donovan

unread,
Aug 30, 2013, 11:02:01 AM8/30/13
to nod...@googlegroups.com

On Aug 29, 2013, at 7:09 PM, Isaac Schlueter <i...@izs.me> wrote:

> On Thu, Aug 29, 2013 at 11:19 AM, Bryan Donovan <brdo...@gmail.com> wrote:
>> Thanks, I just wanted to make sure my understanding was correct in that
>> there really is no problem with the specific case I presented.
>
> That understanding is incorrect.
>
> The exact problem being described in Havoc's post and in mine, is a
> problem with the specific case you presented.

I do understand how it's inconsistent, and I see how it *could* be a problem, but I still haven't seen an example of how my example would cause a real problem in a real application. Let's pretend it's not a public npm for a minute and this 'db' is a home-grown internal module. Say I'm on a team with 10 developers, all working on a REST API using this module. Is there a scenario that would cause a problem in this app due to immediately calling back?

I'm trying to figure out whether it's worth it to go back and change every place in our app that does an immediate callback.

Given that I haven't seen any issues with this in two years, I doubt it's really an issue (again, for an internal app, not a public library). But maybe there are issues we just haven't noticed.


>
> Throw now, or nextTick the callback. Don't do it half-way.
>
> On Thu, Aug 29, 2013 at 7:04 PM, Isaac Schlueter <i...@izs.me> wrote:
>> if (!args.index) { next(new Error('index required')); }
>> if (!args.type) { next(new Error('type required')); }
>
> Ugh, a bug in my example. It should be `return next(new Error(...))`,
> so that it doesn't try to do the async thing if it's already called
> the cb with an error.
>

Scott González

unread,
Aug 30, 2013, 11:56:11 AM8/30/13
to nod...@googlegroups.com
On Fri, Aug 30, 2013 at 10:15 AM, Dean Landolt <de...@deanlandolt.com> wrote:
You say "this sounds like the opposite of promises solving a problem", which I assume would mean promises creating a problem?

No, they're certainly not creating a problem. It's just that the asynchrony inconsistency issue and the design of promises are orthogonal. Until they're not, and then you have a specific implementation or specification of promises that acknowledges this is a real problem and forces the solution into the promise API. For example, Promises/A+ acknowledges this, but Promises/A does not.

Paul

unread,
Aug 30, 2013, 12:48:01 PM8/30/13
to nod...@googlegroups.com
Same with us. 2 years, no problems even though Zalgo is released all over the place with error checking code.

I think we get avoid any problems by just assuming that any callbacks could be called synchronously.

That means never doing something that seems dirty like this:

var thing = doThing(function() {
    thing.doSomething(); // wtf? why should I have to reason about whether this is valid here or not, just assume it is NOT
});

doThing(function(err, thing){
   thing.doSomething(); // never will have any issues reasoning about whether it was called async or sync because the callback has the stuff we need
});

Anyway. My personal opinion is that people are making a bigger deal about it than it needs to be. Just be aware that there could maybe potentially be an issue in your app if you release Zalgo, but don't spend the hours going through and wrapping everything in nextTick just to say you are Zalgo-free even if Zalgo being released has ever doomed you.

Bryan Donovan

unread,
Aug 30, 2013, 12:55:32 PM8/30/13
to nod...@googlegroups.com
Yeah, I really dislike this "pattern".  It's confusing at best.  I never understood the reason for designing an API this way.  I suspect it has something to do with emitting events, like in the http module.

Juraj Kirchheim

unread,
Aug 30, 2013, 2:51:53 PM8/30/13
to nod...@googlegroups.com
I believe that labeling a statement you disagree with "a flat out lie"
is a pointlessly harsh response. But arguably calling that "screaming"
is just as inadequate. I apologize ;)

Now to the point. If you're facing this problem using promises, I
would argue that you're using promises the wrong way - by making wrong
assumptions. When dealing with futures, making any assumption about
the callback being invoked synchronously or asynchronously is wrong
(if for no other reason, then because it is non-essential to the
nature of promises). It's not unlike making two async calls and
expecting the callbacks in the same order. Wrong assumption (although
a not entirely surprising one). That doesn't mean that asynchronous
programming is broken. Only that it requires a little effort to
understand it.

Now it we put aside the fact for a moment, that promises are a
functional concept, and look at them from an imperative perspective,
it is important to notice that, with promises, initiating the
asynchronous operation and defining a handler are two different steps.
With plain callbacks, this is one single step. And the callback is
invoked, regardless whether we're ready for it, or not. This is
particularly problematic, when the callback is synchronous, but the
problem is broader than that.

With promises, we do not have this problem at all. We cannot be caught
with our pants down, because we register the handler when we're ready.
Here's Oleg's code with promises:

var todo = 0;

function done() {
todo--;
if (!todo) callback();
}

todo++;
var a = do_a();

todo++;
var b = something ? do_b() : do_nothing();

a.when(done);
b.when(done);

We make no assumptions about when our callbacks are called. The result
of `do_nothing()` will probably invoke callbacks immediately. It does
not concern us.
Because we have the luxury of choosing an arbitrary point in time to
register our handlers, we can in turn guarantee the one critical
assumption: that we are ready to handle the result at that point. We
have all our state setup, as we see fit.
In consequence, we just as well have the liberty of replacing the last
two lines like so:

do_c().when(function () {
a.when(done);
b.when(done);
});

Or what not. Probably `do_c` will take much longer then just one tick.
Or not. Who knows? Who cares? Also `a` might finish before we get to
register a handler. Again, we do not care. Ultimately, it means, we
can also condense the code and spare ourselves fiddling with counters:

var a = do_a(),
b = something ? do_b() : do_nothing();

do_c().when(function () {
a.when(function () {
b.when(function () {
callback();
});
});
});

Lastly, let's also keep in mind, that this is NOT AT ALL an idiomatic
use of promises. I would say that calling this the "use of promises"
is because you're using promises instead of callbacks is a bit saying
you're doing functional programming because you use `forEach` instead
of plain loops. The point of promises is to have first class values
that can then be composed. That would be something like:

Promise.all(do_a(), something ? do_b() : do_nothing(),
do_c()).when(callback);

No local variables, no state, no trip-wires. If you're really using
promises "the right way", then the presence of captured mutable local
variables (in the sense that you actually write to them) is a pretty
serious smell.

At the bottom line, the problem at hand is easily side-stepped, even
in the most imperative variant, as seen at the beginning. It boils
down to just one ruel: You must make no assumptions that overstep the
boundaries of the solution space your abstractions span, for Zalgo's
realm lies beyond them.

With direct callbacks, you specify the first operation, and right
beside it the code that should handle its result. And only because you
are forced into this highly illogical structure, do you then demand
the invocation to be guaranteed asynchronous, because you have been
forced to define at the beginning of a sequence, what you want
executed at its end - yes, we're all used to it, but quite frankly
it's pretty twisted.

With promises you can explicitly say "execute this no sooner than this
point in the execution flow". Which I think is far more powerful,
especially since (without further noise) you can decide to handle
parallel results in the order that makes sense to you and not in the
order they arrive. So you write code in an order that makes sense to
mortals and you make no superfluous assumptions.

Et voilà, it will not actually even matter whether or not your
promises are forced async or not. What you will get for free is the
ability to execute the same code in an environment that does not have
an event loop but still mimics the same APIs by returning synchronous
promises. Not necessarily useful, but just an illustration of how
powerful it is to assume only the necessary.

Anyway, I see I've written far to much already. But I hope it
clarifies what I mean.

Regards,
Juraj

Scott González

unread,
Aug 30, 2013, 3:13:31 PM8/30/13
to nod...@googlegroups.com
You've actually highlighted the problem in your response. The part where you essentially say "you're doing it wrong." The whole point of forced asynchrony is that it makes it impossible for the user to do it wrong. Yes, if we write the code in the order we want, and we always generate the promise and bind the callbacks in completely separate statements, we can avoid the need for forced asynchrony. You can say the exact same thing about any abstraction (even plain callbacks). But then you're forcing a specific style of coding. One that someone, someday, will violate, probably unknowingly.

Let's look at an example:

// start the ajax request immediately so that we get the response as early as possible
loadAdditionalContentForPage().then( removeLoadingMessage );
showLoadingMessage();

This looks like perfectly reasonable code. loadAdditionalContentForPage() will make an ajax request, and when it's done it will remove the loading message. The ajax request isn't handled by the main thread, so starting it as early as possible in the event loop is beneficial. It's also fairly natural for developers to add their callbacks immediately, even when using promises. However, when IE decides that the ajax request doesn't need to occur because the content is cached, it will invoke the callback immediately, even though it's an async request. Now you've got a loading message that will never go away. Sure, this is easy to fix by doing it "right", but why leave traps?


Trevor Norris

unread,
Aug 30, 2013, 11:36:25 PM8/30/13
to nod...@googlegroups.com
Just want to clarify something. process.nextTick has practically zero cost, even at the micro level. You'll only see the overhead if you're calling it 100k times/sec. Even then it's in the milliseconds. I've been doing optimizations and performance analysis on this for months. Trust me that running your callbacks in nextTick won't slow down your app.

Though if it is, you're doing it wrong. This is not a philosophical opinion. It's empirical fact. Read the section about understanding callback mechanics in this blog post:

http://blog.trevnorris.com/2013/08/callbacks-what-i-said-was.html

dhruvbird

unread,
Sep 1, 2013, 11:59:06 AM9/1/13
to nod...@googlegroups.com
@Bryan: Be careful when using the EventEmitter like pattern because:

var e = new ObjectDerivingFromEventEmitter()
e.makeRequest(request_parameters);
e.on('error', function() { });

Now, if makeRequest() were to emit the 'error' event synchronously, the client code will not be able to catch it.

Even worse is when e.makeRequest() returns a response object and the error is emitted on that response object. There is no way to catch this 'error' event but to raise it in nextTick().



On Tuesday, August 20, 2013 11:12:35 AM UTC-7, Bryan Donovan wrote:
Thanks Scott.

That's basically what I had gathered from googling around.  It seems to me like it's not necessary to wrap in nextTick for most cases then.


On Aug 20, 2013, at 10:58 AM, Scott González <scott.g...@gmail.com> wrote:

The potential issue is if the person consuming your API expects the callback to always be async:

// kick off the async process as early as possible
doAsync( cb );
// do some slightly high cost prep work before the callback is invoked
prepareStateForCallbackWhileWaiting();

Doing the prep work after the async call allows you to reduce the amount of time needed between the start of this tick and the tick when the callback is actually invoked. Interestingly, the reason some people don't like forcing async is for performance gains when the operation can be sync.

You're going to get lots of arguments on both sides.



On Tue, Aug 20, 2013 at 1:47 PM, Bryan Donovan <brdo...@gmail.com> wrote:
I have been writing node.js client code for a couple of years now, and have authored a couple open source libraries, but somehow I missed the memo telling me that I'm supposed to wrap 'synchrounous' callbacks in process.nextTick().  I kind-of understand why that is a best-practice, but what I don't understand is what the drawback is if you don't do it.

For example, I write code like this all the time, and have never had a single problem with it:

function getSomething(args, cb) {
    if (!args) { return cb(new Error('args required')); }
    if (!args.id) { return cb(new Error('args.id required')); }

    SomeDatabase.get({id: args.id}, cb);
}

What are the potential issues with not wrapping those arg checks in process.nextTick()?


Thanks, 

Bryan
You received this message because you are subscribed to a topic in the Google Groups "nodejs" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/nodejs/0TmVfX9z1R0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to nodejs+un...@googlegroups.com.

Bryan Donovan

unread,
Sep 1, 2013, 5:45:22 PM9/1/13
to nod...@googlegroups.com
Right, I get that.
Reply all
Reply to author
Forward
0 new messages