Next time you're about to define a function with a callback argument, don't.
No, do. It's the prevailing style in Node.js and elsewhere, and so there exist tools to transform them (syntactically or otherwise) into more palpable styles:
It's an interesting presentation, but it neglects an obvious solution to the nested callbacks problem, which are the various "async"-like control-flow libraries out there. More importantly, it neglects a critical problem with all of these approaches, which is being able to observe asynchronous state (from a debugger, repl, or whatever) when things go wrong -- when a callback (or event) you expected doesn't fire, or the same one is called (or emitted) early or too many times. IME, these are the really hard problems to debug in async JavaScript, and there are improvements available (I wrote node-vasync for this), but I still wouldn't say it's well solved by any of these approaches.
Agreed. I think I saw that async in C# has exceptions that behave as one naively expects they should. That would make JS so much more awesome, even with callbacks. The worst part of them isn't the pyramid, IMO, so much as they all the horrible repeated "if error...."
"Nested spaghetti" strikes me as a mixed metaphor. I'd have to ask an expert on Italian cuisine, but I don't think spaghetti nests. Literary aspects aside, I don't think the "pyramid of doom" is what is meant by spaghetti. In the pyramid, it's easy to see the sequence in which things happen. In spaghetti code, it's hard to see the sequence -- that's the problem.
The fact that the pyramid makes it easy to see the sequence, i.e. follow the chain of causation, and easy to find the callbacks, argues that there is a place for it. Additionally, it makes it easy to transfer state through the chain, since each callback has access to the previous one's variables.
The other patterns, which probably all have appropriate uses, too, would seem to carry a greater risk of spaghetti -- i.e. control that jumps around unexpectedly and is hard to follow.
It's easy to see what's happening if everything is written in one file. It's hard if your callback are fired from and/or go into minimized library code you don't control or don't want to debug. You may have your own pyramid, but it's really sitting on someone else's pyramid.
Yeah, the real problem for me with the nested case is error handling. It's all manual, and the default (unless you're very careful) is that errors do not propagate and will instead simply be lost and ignored.
This "PubSub" pattern is really trying to recreate RxJS but without the expressiveness - if you find yourself doing this kind of work a lot, you should check it out
IME, most clutter from async logic in JavaScript, however it's dressed up, only happens at all because of implementing something that is fundamentally sequential and synchronous using the wrong tools for the job. The slide "A JS-er's lament" was a great demonstration of how fundamentally broken the current JS model is.
Put another way, the obviously missing pattern to me is to just move the concurrency up to the level of having multiple threads, and then use synchronous interfaces for synchronous operations. Now that Web Workers are starting to become more widely supported, I don't understand why we aren't heading for synchronous APIs as fast as humanly possible.
The bit about AMD didn't really fit in with the rest. Why start talking about module loading when you were discussing async earlier? They are completely separate issues.
While looking through the slides, I was wondering:
Is there some community-standard way of naming/declaring a JS function to let the programmer know just by reading the function's name that it will return a promise instead of a final computed value? I'm interested in knowing how people handle this. It's easy to forget that a function doesn't return a promise if the function name makes no mention of it, so I just make it explicit in the function name... but that's just how I handle it.
Promises are about as clean as you can get in a purely async environment.
Yet, notice how no one shows off how easy it is to write a loop. Oh, and what happens when your code throws an exception? It's as though that path halted forever.
Also, what happens when you have a method defined as
result = foo(args);
that now needs to perform an async operation? Enjoy refactoring every call site.
Sometimes require.js feels a bit heavy for what is mostly a straightforward concept (AMD), so I use a much simplified version of the pattern: https://gist.github.com/potch/4156519
https://github.com/0ctave/node-sync https://github.com/BYVoid/continuation https://github.com/JeffreyZhao/wind https://github.com/Sage/streamlinejs