when.js and nextTick

279 views
Skip to first unread message

Brian Cavalier

unread,
Oct 20, 2012, 2:24:05 PM10/20/12
to cuj...@googlegroups.com
Whether when.js should always guarantee asynchronous promise resolutions (i.e. nextTick) has been an open question in my mind from the beginning.  Promises/A doesn't specify. The biggest advantage in my mind is the consistent behavior of callbacks in the current tick.  Dominic Denicola describes it better in his Promises/A test suite.  The biggest downside is raw performance.  Currently, the performance of when.js's raw method calls is very good.  In fact, in non-v8 VMs, and in v8 with when.js's paranoid mode turned off, it appears to be orders of magnitude faster than some other libs, and percentages faster than @medikoo's already-very-fast deferred lib.  Remember, though, that's without using nextTick.

As usual (and as stated in the promises-perf-test README), though, performance isn't everything.

It's trivial to add nextTick to when.js.  It does feel like the "right" thing to do theoretically.  On the other hand, if you never run into a situation where it helps you, it's a significant performance hit.  To be fair, I've heard a rumor that node 0.9's nextTick will be much faster.  Of course, browsers must still rely on other mechanisms--most modern browsers offer something relatively fast, like MessageChannel, setImmediate, etc., but others still degrade to setTimeout(..., 0), which is awful.

There's an open issue with more discussion here, so I'm just trying to open this up again and get more input.  I'd love to hear what you think.

Thanks!

Karolis Narkevičius

unread,
Oct 20, 2012, 2:44:58 PM10/20/12
to cuj...@googlegroups.com
So far, I can't say I ever ran into any issues with the current when.js behaviour. To be honest when coding in JavaScript you tend to watch out for things like that anyway. I feel like you should always try to structure code in a way where sync/async callback behaviour doesn't matter.

The only annoying things is how everyone overnight decided to be like "ooh.. and there's when.js but it's not good cause it doesn't always guarantee async promise resolutions".

Cheers,
Karolis



--
You received this message because you are subscribed to the Google Groups "cujojs" group.
To post to this group, send email to cuj...@googlegroups.com.
To unsubscribe from this group, send email to cujojs+un...@googlegroups.com.
Visit this group at http://groups.google.com/group/cujojs?hl=en.
To view this discussion on the web visit https://groups.google.com/d/msg/cujojs/-/ge1zA1RuFX0J.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Brian Cavalier

unread,
Oct 21, 2012, 7:12:51 PM10/21/12
to cuj...@googlegroups.com
Thanks!  Appreciate the support, as always.

 I've tended to agree ... if you write your code without making any assumptions about when a callback will be invoked, you're fine.  Wire.js is a good example.  There are tons of promises flying around, and we've never really had a problem I could attribute to lack of nextTick.

What I would really love to see are some compelling, real world examples of how/where nextTick helps.  Every example I've seen is either trivial/unrealistic, or could be refactored easily to work without guaranteeing nextTick.

John Hann

unread,
Oct 21, 2012, 8:39:55 PM10/21/12
to cuj...@googlegroups.com
curl.js doesn't wait for next-turn, either. Although this has made the code more complex, it has also made curl faster than other loaders (drastically faster during development). 

On the down side, I've had to create three pull requests to correct third-party libraries that assume AMD's define() resolves next-turn. 

These corrections have been very trivial.  To me, this a no-brainer. I'd much rather have to create an occasional pull request in order to have curl.js be blazing fast than have the opposite situation. 

It sounds to me like we're in a similar situation with when.js. If so, I'm in favor of same-turn behavior unless a
cujo.js user has a good case for us to do the opposite. Then we can offer it as an option. 

Just my 2¢. :)
To view this discussion on the web visit https://groups.google.com/d/msg/cujojs/-/BViSDYEMy9kJ.

Brian Cavalier

unread,
Oct 22, 2012, 11:03:21 AM10/22/12
to cuj...@googlegroups.com
On Sunday, October 21, 2012 8:39:57 PM UTC-4, unscriptable wrote:
curl.js doesn't wait for next-turn, either. Although this has made the code more complex, it has also made curl faster than other loaders (drastically faster during development). 

Interesting ... can you think of any examples of the added complexity that might transfer over to someone using promises?  I know curl uses its own simple promise impl internally, so I figure there may be.
 

These corrections have been very trivial.  To me, this a no-brainer. I'd much rather have to create an occasional pull request in order to have curl.js be blazing fast than have the opposite situation. 

It sounds to me like we're in a similar situation with when.js. If so, I'm in favor of same-turn behavior unless a
cujo.js user has a good case for us to do the opposite. Then we can offer it as an option. 


That's basically my feeling right now, too.  If there are *real* situations (not the typical trivial examples of out-of-order console.log or myVar++) where nextTick is important, we should consider it--possibly as an option like you said.  It might also be interesting to create branch, add nextTick, and the re-run the performance tests to see exactly what the impact is.

Domenic Denicola

unread,
Oct 23, 2012, 10:56:07 PM10/23/12
to cuj...@googlegroups.com
I think it's important. Things can be "trivially refactored" to not depend on the async behavior, but you shouldn't have to even do that, and in some cases (where things can be nondeterministically sync or async, e.g. a cache) it's just a bug waiting to happen that you probably won't be able to catch in your unit tests in time, unless you create two stubs for every promise-providing part of your system (i.e. sync and async versions).

I'd prefer async by default, with a "blazing fast" option to turn it off.

The performance hit will be almost nonexistant in Node >=0.9, due to https://github.com/joyent/node/commit/4e5fe2d45aef5aacf4a6c6c2db28f0b9cc64f123.

In browsers there are nice solutions all the way down to IE6: https://github.com/NobleJS/setImmediate/blob/master/setImmediate.js#L170-188

One way to avoid all that work is to let nextTick = setImmediate if available, and point people to the repo if they want non-IE10 support.

Brian Cavalier

unread,
Oct 24, 2012, 12:32:39 PM10/24/12
to cuj...@googlegroups.com
I'm open to being convinced.  Here's my perspective right now:

- Consistency is good, but Javascripters deal with mixing sync and async all the time.  If you write any JS code at all you're probably used to it.
- Statements like "always async is better", or "mixing sync and async is bad" alone are not convincing for me.  I would like to see real examples of situations where async resolutions solve real, non-trivial problems.
- wire.js uses promises very extensively via when.js, and we've used it to interoperate with promises from Dojo, jQuery, and Q, and have never had a problem.  Of course, it's possible I've just gotten used to writing my code a certain way.
- In my experience, next-tick is not a golden hammer, and can cause it's own kind of confusion when combined with other things like event emitters, streams, etc. whose innards may not use next-tick, or *do* use it in way that makes debugging extremely difficult due to tons of interleaving.

The "test it twice" argument does resonate with me in that I agree it's ridiculous to have to write two tests for functions that deal with promises.  Again, though, I can't recall ever having a problem and realizing that I needed to write two tests.

Do you have some non-trivial examples or a thorough explanation you can share or point me to?  Help me understand :)

I feel fairly strongly that a promise library should be as "performance invisible" as possible.  I'll def have a look at the NobleJS setImmediate polyfill.  If it really is "full speed" across browsers that cujojs cares about, then obviously, that'd make me feel better about the performance hit.

Brian Cavalier

unread,
Oct 24, 2012, 12:40:09 PM10/24/12
to cuj...@googlegroups.com
Another quick q.  By "async resolutions" do you mean that *each* promise handler must be called in its own, separate tick (i.e. 1 nextTick per handler), or do you mean that all handlers may be called in the same tick, as long as that tick is not one in which they were registered with .then()?

The latter would also make me feel better about performance, and appears to be what rsvp.js does, if I understand the code correctly.

Thanks!


On Tuesday, October 23, 2012 10:56:07 PM UTC-4, Domenic Denicola wrote:

Domenic Denicola

unread,
Oct 24, 2012, 11:24:16 PM10/24/12
to cuj...@googlegroups.com

Yes, it’s the latter (as long as they are called in order, and do not interfere with each other e.g. by a thrown exception by the first preventing the second from executing). All of the nextTick-like methods, whether they be process.nextTick, or setImmediate, or setTimeout(, 0), will queue up multiple calls in this way. (Although, see https://github.com/tildeio/rsvp.js/issues/20.)

 

--

You received this message because you are subscribed to the Google Groups "cujojs" group.
To post to this group, send email to cuj...@googlegroups.com.
To unsubscribe from this group, send email to cujojs+un...@googlegroups.com.
Visit this group at http://groups.google.com/group/cujojs?hl=en.

To view this discussion on the web visit https://groups.google.com/d/msg/cujojs/-/csS03osEFgEJ.

Brian Cavalier

unread,
Oct 25, 2012, 5:15:37 PM10/25/12
to cuj...@googlegroups.com
After much thinking and reading info from Domenic and others, I've come to the conclusion that my only reservation is performance.  Outside of performance, I think the safety advantages of async win.  It's not a trivial reservation for me, though, since wire.js uses tons of promises and is primarily in use in browser envs.  I'm betting that for "small" numbers of promises, the hit won't be noticeable since the time will be totally dominated by the async operations themselves (e.g. network latency in XHR, etc.), but I want to get a feel for large numbers of them.

So, I'm planning to do some tests by branching an async-only version of when.js.  I want to see some real numbers, especially with wire.

If the performance is acceptable, we'll probably put it on the slate for 2.0.

To unsubscribe from this group, send email to cujojs+u...@googlegroups.com.

johlrogge

unread,
Oct 26, 2012, 8:12:50 AM10/26/12
to cuj...@googlegroups.com
I'm also very interested in the promise-heavy case. I'm currently playing with some page building code that relies heavily on many promises and all future plans seem to call for even more of them so I am definitely for performanceinvisible promises. For me a promise is like a cons in lisp, it is this tiny little thing with an insane amount of power and you want to be able to use lots of them without artificial performance considerations... I am not only using promises to get the occational asyncronous resource but for things like creating infinite streams of data etc... The latter case creates lots of the little buggers...

Brian Cavalier

unread,
Oct 26, 2012, 10:26:10 AM10/26/12
to cuj...@googlegroups.com
I hear ya.  I've done some very limited testing in v8+node 0.8.10, and the performance is acceptable.  It's an order of magnitude (almost exactly 10x, in fact) slower than when.js in sync mode, but in most cases still faster than other sync implementations, such as jquery and deferred.  One open question is how that performance scales as the number of promises increases, and how it will vary across particular usage patterns.

Anyway, don't take any of this perf info to hear yet, other than maybe as an initial indication that it's not awful :) There's still plenty of testing to do.

Mariusz Nowak

unread,
Oct 29, 2012, 11:28:25 AM10/29/12
to cuj...@googlegroups.com
Adding to discussion, I think introducing asynchronicity in library that helps to deal with asynchronicity is not great, also nextTick should rather be decided by the programmer that uses library and not library itself, at least I work with stuff that does some special stuff on nextTick, and if I would use promise library that on resolved promises calls `then` callbacks in nextTick, I would be in trouble and would need to search for workarounds.

I understand "consistency" point of view, but I think it sounds good just in theory and in practice it introduces problems rather then solves them.

The rule I think we must obey is that: *Asynchronous function that returns promise, must not return resolved promise*, having that in most cases we know whether we deal with resolved or unresolved promise and there are no surprises.

johlrogge

unread,
Oct 30, 2012, 8:09:20 AM10/30/12
to cuj...@googlegroups.com
10x may be something you want on your side in some applications and may be totally irrelevant in others. I'm not sure I see the advantage with implicit nextTick. Is it so that one can be more sloppy about in which order one does things? A pointer to a simple code example would help me understand I think.

From my current understanding I tend to agree with Mariusz, nextTick or no nextTick should be in the programmers control. If it is synchronous the programmer can choose to use nextTick. if it it always asynchronous the programmer can not override that behaviour without configuration options or something.

or... when.imediate(promise).then(...) could be one way to explicitly create a promise that is skipping the nextTick behaviour? (feel free to choose a better keyword than immediate.
Perhaps explicitly requiring a promise that is fast when it is called for is the best. It is probably not the most common case though it is the case for me in my current project :)

Brian Cavalier

unread,
Oct 30, 2012, 6:06:18 PM10/30/12
to
After more testing, it's actually around 2x when a fast nextTick like node.js is available, and so far, seems to scale linearly with the number of promises you pile on--I've run tests up to 1 million chained promises, at which point v8 falls over due to memory errors.

Believe me, I do hear you that sometimes you want/need that 2x.  Having to degrade to something like setTimeout in old browsers stinks, but thankfully there are faster techniques (read: "hacks") available even back to IE 6.  Of course, the multiplier will be more than 2x with those.

As I understand it, the real problem is that the behavior in the current tick cannot be guaranteed to be consistent without forcing nextTick for resolutions.  For example, if some library hands you a promise, without a guaranteed nextTick, you simply can't tell if it's going to call your callback immediately or later, since the promise may have already resolved.  To address Mariusz's suggestion: it's one thing to say "we should just always return unresolved promises", but it seems infeasible to get every developer whose code returns promises to program that way, and we all make mistakes.

For some of us, who are comfortable working with promises, it is easy to refactor to deal with that situation.  For users who are not as experienced, though, it could be a bad situation, since they might simply test it under one set of conditions and call it done.  Then later, under another set of conditions, their app breaks for no apparent reason.  Async is hard, so I think it's important to help that situation, if we can.

To me, the ideal would be guaranteeing consistent behavior while also providing a very fast implementation.

Just to give you an idea of relative test results: In my testing so far, even with the async hit, when.js is still several times faster (without Object.freeze(), which is being removed in 1.6.0 until v8 fixes the perf problem) than jQuery Deferred in every test I've run, and faster than deferred in most of those tests.  Both of those libraries are synchronous.  Caveat: that's all in node, with a fast nextTick.

Another interesting data point: In v8, asynchronous when.js without freeze() is actually quite a bit faster than synchronous when.js with freeze().  So, if your code ran at an acceptable speed using when.js < 1.5.0, then it'll be fine (faster, in fact) in 2.0.  Yeah, the v8 freeze() hit is crazy.

I feel pretty good about the perf numbers so far, but I do need to do more testing in browsers.  If we go async for 2.0, we're still planning to offer an option for sync mode, so you can opt in to that to get the sync speed if your application needs it.  Again the thinking is that being consistent and fast by default, and offering a "be careful" but blazingly fast opt-in seems like a good approach

Brian Cavalier

unread,
Oct 30, 2012, 6:12:58 PM10/30/12
to cuj...@googlegroups.com
Also: I'll plan on cleaning up my tests and posting actual numbers later this week.  That'll give us some real evidence to discuss!


On Tuesday, October 30, 2012 5:25:18 PM UTC-4, Brian Cavalier wrote:
After more testing, it's actually around 2x when a fast nextTick like node.js is available, and so far, seems to scale linearly with the number of promises you pile on--I've run tests up to 1 million chained promises, at which point v8 falls over due to memory errors.

Believe me, I do hear you that sometimes you want/need that 2x.  Having to degrade to something like setTimeout in old browsers stinks, but thankfully there are faster techniques (read: "hacks") available even back to IE 6.  Of course, the multiplier will be more than 2x with those.

As I understand it, the real problem is that the behavior in the current tick cannot be guaranteed to be consistent without forcing nextTick for resolutions.  For example, if some library hands you a promise, without a guaranteed nextTick, you simply can't tell if it's going to call your callback immediately or later, since the promise may have already resolved.  To address Mariusz's suggestion: it's one thing to say "we should just always return unresolved promises", but it seems infeasible to get every developer whose code returns promises to program that way, and we all make mistakes.

For some of us, who are comfortable working with promises, it is easy to refactor to deal with that situation.  For users who are not as experienced, though, it could be a bad situation, since they might simply test it under one set of conditions and call it done.  Then later, under another set of conditions, their app breaks for no apparent reason.  Async is hard, so I think it's important to help that situation, if we can.

To me, the ideal would be guaranteeing consistent behavior while also providing a very fast implementation.

Just to give you an idea of relative test results: In my testing so far, even with the async hit, when.js is still several times faster (without Object.freeze(), which is being removed in 1.6.0 until v8 fixes the perf problem) than jQuery Deferred in every test I've run, and faster than deferred in most of those tests.  Both of those libraries are synchronous.  Caveat: that's all in node, with a fast nextTick.

Another interesting data point: In v8, asynchronous when.js without freeze() is actually quite a bit faster than synchronous when.js with freeze().  So, if your code ran at an acceptable speed using when.js < 1.5.0, then it'll be fine (faster, in fact) in 2.0.  Yeah, the v8 freeze() hit is crazy.

I feel pretty good about the perf numbers so far, but I do need to do more testing in browsers.  If we go async for 2.0, we're still planning to offer an option for sync mode, so you can opt in to that to get the sync speed if your application needs it.  Again the thinking is that being consistent and fast by default, and offering a "be careful" but blazingly fast opt-in seems like a good approach

On Tuesday, October 30, 2012 8:09:20 AM UTC-4, johlrogge wrote:

Domenic Denicola

unread,
Oct 30, 2012, 6:14:58 PM10/30/12
to cuj...@googlegroups.com

Worth testing against Node 0.9 as well, where nextTick has been substantially sped up.

To unsubscribe from this group, send email to cujojs+un...@googlegroups.com.

To view this discussion on the web visit https://groups.google.com/d/msg/cujojs/-/ViNMJkjXwyIJ.

Mariusz Nowak

unread,
Oct 30, 2012, 7:27:35 PM10/30/12
to cuj...@googlegroups.com


On Tuesday, October 30, 2012 10:25:18 PM UTC+1, Brian Cavalier wrote:
To address Mariusz's suggestion: it's one thing to say "we should just always return unresolved promises", but it seems infeasible to get every developer whose code returns promises to program that way, and we all make mistakes.


This is actually not just my suggestion, but golden rule in asynchronous programming. You won't find any asynchronous function in Node.js API that calls callback immediately  also you won't find it in any well written package. 

Same way if you work with promises, you should never return fulfilled promise from asynchronous function, if it happens it's a bug that needs to be fixed not just mistake, and fixing that by forcing nextTick by promise library internals is I think sloppy route.

 

On Tuesday, October 30, 2012 8:09:20 AM UTC-4, johlrogge wrote:

Brian Cavalier

unread,
Oct 31, 2012, 7:42:48 AM10/31/12
to cuj...@googlegroups.com
Just ran a few quick tests with asyncified when and node 0.9.3's nextTick was about 10 - 25% faster than 0.8.14's on my machine.  The difference varied based on the situation, which is probably to be expected.

Brian Cavalier

unread,
Oct 31, 2012, 8:09:54 AM10/31/12
to
On Tuesday, October 30, 2012 7:27:35 PM UTC-4, Mariusz Nowak wrote:
This is actually not just my suggestion, but golden rule in asynchronous programming. You won't find any asynchronous function in Node.js API that calls callback immediately  also you won't find it in any well written package. 

Yes, we're in agreement on that point.  But, it sounds like you've said that any library function that accepts a callback should not call that callback in the current turn.  If so, then wouldn't that imply that, since .then() is a library function that accepts a callback, .then() should not call its callbacks in the current turn?

It also seems like never returning a fulfilled promise could make some situations messier.  For example, what if a value that would normally be fetched asynchronously is available immediately in a local cache?  It seems quite clean to me for a promise-based implementation to simply return a fulfilled promise in that case.

Same way if you work with promises, you should never return fulfilled promise from asynchronous function, if it happens it's a bug that needs to be fixed not just mistake, and fixing that by forcing nextTick by promise library internals is I think sloppy route.

Ah, maybe the difference in viewpoints here is that Domenic is saying that the responsibility of ensuring current-turn callback safety belongs with the promise implementation, whereas you are saying that it belongs with the library (or application) code that is using the promise implementation?

Or put another way, ultimately, we seem to agree that ensuring callbacks are not invoked in the current turn is important, but that we disagree on where the responsibility of ensuring that belongs.

Mariusz Nowak

unread,
Oct 31, 2012, 9:01:26 AM10/31/12
to cuj...@googlegroups.com


On Wednesday, October 31, 2012 1:09:15 PM UTC+1, Brian Cavalier wrote:
On Tuesday, October 30, 2012 7:27:35 PM UTC-4, Mariusz Nowak wrote:
This is actually not just my suggestion, but golden rule in asynchronous programming. You won't find any asynchronous function in Node.js API that calls callback immediately  also you won't find it in any well written package. 

Yes, we're in agreement on that point.  But, it sounds like you've said that any library function that accepts a callback should not call that callback in the current turn.  If so, then wouldn't that imply that, since .then() is a library function that accepts a callback, .then() should not call its callbacks in the current turn?


I don't perceive .then() as asynchronous function, it doesn't initialize any asynchronous tasks.



It also seems like never returning a fulfilled promise could make some situations messier.  For example, what if a value that would normally be fetched asynchronously is available immediately in a local cache?  It seems quite clean to me for a promise-based implementation to simply return a fulfilled promise in that case.
 

When speaking of memoized version of asynchronous function that aims to be *transparent wrapper over asynchronous function* I would always call callback in nextTick, it's because it should directly reflect behavior of underlying function (I've solved it that way in Memoize: https://github.com/medikoo/memoize/blob/master/lib/ext/async.js#L35 ). .then() is not asynchronous function and is not meant to be reflection of any.

Writing asynchronous function that incorporates caching by itself, I would call a bad practice, it's user of a library that should decide whether he wants function to be memoized. For example there is no internal caching in Node.js async functions, while in many cases you would definitely benefit from that.


Same way if you work with promises, you should never return fulfilled promise from asynchronous function, if it happens it's a bug that needs to be fixed not just mistake, and fixing that by forcing nextTick by promise library internals is I think sloppy route.

Ah, maybe the difference in viewpoints here is that Domenic is saying that the responsibility of ensuring current-turn callback safety belongs with the promise implementation, whereas you are saying that it belongs with the library (or application) code that is using the promise implementation?

Or put another way, ultimately, we seem to agree that ensuring callbacks are not invoked in the current turn is important, but that we disagree on where the responsibility of ensuring that belongs.


Yeah, we may put it that way :) Programmer should be in control of nextTick, it's easy to configure one, but workaround one that's forced by internals may be very tricky and definitely would not be clean.

I remember once someone submitted an issue for EventEmitter in which he suggested that events should be called in nextTick internally. Imagine how problematic that might be if EventEmitter would behave like that, and that case is not that far from .then() case. In EventEmitter we are vulnerable to similar programmer's mistakes:

var Foo = function () {
   // ...
   this.emit('init');
};

// ...
foo = new Foo();
foo.on('init', function () { .. });

 
Reply all
Reply to author
Forward
0 new messages