You have a number of ways of keeping a handle on async code now.
1. Native ES6 promises will cover most of your basic needs. See https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise https://developer.mozilla.org/en/docs/Web/JavaScript/Referen.... In fact, unless you have very specific needs not covered by native promises, you shouldn't drop a third party library into your project.
2. For more advanced operations on promises, use Bluebird. See http://bluebirdjs.com http://bluebirdjs.com and http://bluebirdjs.com/docs/api-reference.html http://bluebirdjs.com/docs/api-reference.html. It has a ton of features built on top of native ES6 promises. If you're using an older version of Node, it also acts as a polyfill. It can also make it easy to work with libraries that only expose a callback-based interface. So yeah, Bluebird is the shit.
3. co is a generator-based control flow library that can make your async code look more like synchronous code. It's pretty cool, but I haven't personally used it in a project, nor do I know of anyone else who uses it. It builds on top of promises and generators, and isn't very hard to understand under the hood. See https://github.com/tj/co https://github.com/tj/co. I wouldn't recommend it, but it might be something to play with because ...
4. ... async/await is coming to JavaScript! At a very high level, this pair of keywords is syntax sugar for what co already does as a library. This is the final stage in the evolution of taming callback hell in JavaScript, and it builds on top of everything that has come before (promises and generators). Here's a tutorial: https://blog.risingstack.com/async-await-node-js-7-nightly/ https://blog.risingstack.com/async-await-node-js-7-nightly/
I am confused. Your recommendations 1 and 2 seem to conflict with one another. I use Bluebird now for promisification, and I also used a few other features including the resource management (disposers). Does this mean that I will be using Bluebird and not native promises for the foreseeable future? Or can/should I use Bluebird with native promises? (Or would that just make things slower if it is even possible?)
Automatic promisification and the disposer pattern are both Bluebird-specific features that native ES6 promises don't support, so you'll have to continue using Bluebird if you rely on them.
However, promises returned by Promises/A+ compliant libraries -- and this includes native ES6 promises -- are interoperable with each other. That means you can mix Bluebird promises with native promises for the most part. E.g, passing an ES6 promise to Bluebird's Promise.all will work just fine. This works up to a point, and breaks when you try to use a Bluebird-specific function that's expecting additional functionality to be attached to the promise object.
I don't see why mixing Bluebird promises with ES6 promises would make anything slower. You shouldn't worry about this.
Bluebird is faster than native promises as well.
http://softwareengineering.stackexchange.com/questions/278778/why-are-native-es6-promises-slower-and-more-memory-intensive-than-bluebird http://softwareengineering.stackexchange.com/questions/27877...
Native promises and bluebird use different microtask queues on Node which makes interop incorrect (which is also one of the reasons why bluebird is ES6 spec non compliant). See https://gist.github.com/anonymous/9936c695f2c29e79a0c1a0dec863abea https://gist.github.com/anonymous/9936c695f2c29e79a0c1a0dec8...
This might lead to subtle ordering bugs, I would recommend against using both together.
Using bluebird and native promises together may result in slower performance because V8 has fast paths for native promises which fail for bluebird.
In what way is this incorrect? Those are three separate promises; no code can be correct that depends on whether one resolve() call finishes before an independently started resolve() call, right?
If the ordering is important, then you should be using
a()
.then(()=>b())
.then(()=>c())
Assuming anything about the completion order of:
a():
b();
c();
Where a, b and c are async tasks of any kind (be they classic old callbacks, Promises, Observables, whatever) is just asking for trouble. The whole point is that you don't care about when it finishes, you just want to know that it has finished. If the order is that important and you don't want to handle managing it, you should just write standard synchronous code.
Also highly recommend the npm async library[1] even though the hipster way is now native async/wait or promises. caolan/async has some amazing sugar on nearly every use-case the most common for me being async.auto().
[1] https://github.com/caolan/async https://github.com/caolan/async
My understanding was that Node didn't support native ES6, is that no longer the case? Or are you talking about using a transpiler?
Node supports all of ES2015 and ES2016 and will soon support ES2017 in the next few months or so.
So yeah, for most things you don't need to use a transpiler.
Node has supported all of ES6 except modules since 6.0.
A year or two ago that was the case. Then io.js forked, implemented a bunch of features and later was brought back into the NodeJS fold. Since then, Node has been a lot more proactive about keeping up with newer specs.
You can always check here: http://node.green http://node.green
Promises don't cover a lot of scenarios in async usage. When building some node apps, especially toolage, doing everything async just isn't possible, or is a lot more work for little benefit. The second you use a promise, anywhere, working with its output is now also async, you cannot 'wait' for it. I don't know if async/await actually does this or not, but until that functionality exists in JS, working with node, for me, is a pile of junk code, especially when libraries force you to use them asynchronously.
>working with its output is now also async, you cannot 'wait' for it ... , but until that functionality exists in JS, working with node, for me, is a pile of junk code, especially when libraries force you to use them asynchronously
Asynchronicity is something that Node makes explicit and necessary for I/O, and it's practically a core principle of Node and javascript. If you try to avoid asynchronicity entirely, then you're not going to get much further than you would if you tried to avoid classes in Java.
Async/await is a very useful syntax sugar to working with promises, but it's not built to let you ignore asynchronicity entirely.
Grandparent: "lack of a definitive library"
Parent: "You have a number of ways"
Do you see the irony?
I've used the `co` library at work to extensively trim down and simplify a lot of server-side Node.js code recently. It's worked out very well compared to the very Promise heavy code we had before, especially since so many database libraries now make use of promises. I would highly recommend it if you're going to be stuck using Node v6 for the next 2-4 years.
On the front-end, we're using TypeScript 2.1.5, so we've been able to make good use of async/await there. The only tricky parts are dealing with AngularJS, due to the way it handles change detection via scope digests.
Both solutions still require the use and understanding of ES6 promises, especially when dealing with code that only works with callbacks.
http://callbackhell.com/ http://callbackhell.com/
I think the mental gymnastics needed to write non blocking code also makes you understand the flow of your program, witch allows good abstractions. After a while it becomes a second nature and you get a mental picture of all branches.
Thinking about code as events (button.onclick = showPicture), makes you a better programmer, as this is how a computer work. And when your program has to scale across several CPU's or servers, it will come naturally to you.
Multi threaded solutions can be easier at first, but when you need to have threads that communicate, and handle locks etc, that too becomes hard.
Yes! Callback hell is a gift that forces you to refactor code until it's easy to understand and your abstractions are correct.
Promises are a band-aid that gives you shallower indentation, and then simply hides the callback complexity in invisible objects that are even harder to debug.
The problem is the modern web development world is all about creating two classes of programmer: framework programmers who control their entire stack, and application programmers who suckle at the teat and can't modify libraries, only writing composite works out of building blocks.
Application programmers only feel safe when they have an exhaustive palette of libraries that they can use, because they know they can't modify anything. They thrive on feeling taken care of.
Framework programmers only feel good about themselves when they know they're working on something so complicated that application programmers will never be able to understand it. They enjoy creating an simple interface for the common man, while solving "hard problems" in abstract domains. They thrive on superiority.
This creates an impermeable layer that can never be refactored (framework programmers don't have access to app code, app programmers can't modify the frameworks). So when someone encounters callback hell, the necessary refactoring to find the right interfaces is impossible.
Promises work well in this dead layer, because they create a clear boundary of responsibility between these two kinds of programmers. But the cost is thousands of tiny invisible state machines, with no labels or semantics.
> Callback hell is a gift that forces you to
> refactor code until it's easy to understand
That doesn't sound like a gift to me. Unless you meant they are so painful as an abstraction that they gifted us with promises and then async/await.
Almost everyone here has worked with callbacks and you'll be hard-pressed to find people that felt like they improved the code base.
Just tiny changes to the logic, like an if-statement with branching async behavior, would cause disproportionally large changes in the callback structure, pretty much touching every line/indentation.
The rest of your post is really negative and judgmental. Not sure why you felt it was necessary.
Oh, sorry... in editing I forgot the bit about where promises fit in the framework/app paradigm. Added a paragraph.
refactoring is better then an extra if and argument. and force you to understand the code and result in less bugs.
This is probably not the answer you're looking for, but you could look into ways that make callbacks enjoyable. The syntactic sugar provided by CoffeeScript goes a long way to make readable (and, dare I say, elegant) what would be a tangled mess in straight up JavaScript:
# Get profiles LIST as JSON
.post "/list", (req, res) ->
db.maria.query getProfilesQuery, { userId: req.session.passport.user }, false
.on "result", (result) ->
rows = []
res.status(200)
result
.on "row", (row) -> rows.push row
.on "error", (error) -> logger.warn error
.on "end", ->
res.json rows
res.end()
5 callbacks here but they don't impede readability.
Maybe it's just me, but this would look infinitely more readable to me if I could tell where each callback starts and where it ends, i.e. in plain JavaScript.
What impedes readability here is that you don't simply have an async function `getProfiles({ userId })` that gives you a list of profiles, either taking a callback or returning a promise.
A lot of that is pointless noise. You don't need to set the status code to 200 or call end(). `res.json(profiles)` will do.
And these days, Coffeescript is just Javascript with some optional parens. Can't think of many upsides in 2017 to not just using ES6.
> it encourages a lot of callback hell we've found at my company
This is a very odd comment. async provides the same level of indentation as Promises does:
async.waterfall([
function(){},
function(){},
])
Here's promises doing the same thing:
function()
.then(function(){})
.then(function(){})
One's a list of functions to be run in order, the other uses method chaining. You may prefer one or the other but saying async 'encourages callback hell' is about as logical as saying promises does.
__Edit__: further to my own comment. The people I know who know their stuff (ie, work on JS itself) and like/prefer Promises do so because they believe that functions should return values.
I don't think anything will be better than either solution until async/await gains wide support. Async/await needs return values, so hence promises, so it's definitely worth knowing promises. But yeah, right now async is fine.
Promises also work better with Typescript, since it's well defined which type each step returns and which the next function expects. That means the .then(a => next(a)) cascades can be typechecked well. async.waterfall doesn't seem to be typecheckable.
It also provides more specific control flow functions that are bug-prone to roll your own with promises. I've learned a lot by looking at the source!
Yea but when using promises with async/await its very beautiful.
await asyncFuncOne();
await asyncFuncTwo();
No more weird water-falling, and finally proper exception handling. The only gotcha is the fact that we cant awaits at the root of a module, and we need to wrap it in a async function.
The huuge difference once you understand its implications is that you can:
return function().then(() => ...).then(() => ...)
...and the caller of you function can decide to chain even more stuff after, in the simplest case. Most obvious benefits, though not the biggest, is that you simply can return a promise ad drop the ugly extra callback argument that you function needs to have. For example:
function foo(cb) {
async.waterfall([
function(){},
function(){},
])
}
becomes:
function foo2() {
return function()
.then(function(){})
.then(function(){});
}
...and this way the logic ends up where it should be, in the caller, not the callee!
Another simple to explain benefits are the fact that you can .then() a promise 10 times and what it does will only happen once, and then the other times you'll just consume the result. You don't need to care if something
is (a) already computed, (b) in the process of being computed, (c) scheduled for computation later etc., you can write the exact same code for all cases. Because you're working with values that have dependencies between this, not with processes that need to happen in a certain order.
The bigger benefits show up when you write more and more code and you see how beautifully it all untangles with promises.
And the fact that once you learn promises you can carry further the knwledge to other async patterns like co and async/await (they are all understandable in terms of promises).
Do yourself and everyone you work with a favor and move away from async please, especially in a team promises make everything so much easier to understand! Same advice of you're writing nodejs libraries: have you functions return promises instead of take callbacks! Drop the intellectual lazyness and grow beyond "async by callbacks", it's the worst coding style ever. And everyone will be able to convert almost instantly from promises to async/await since the logic is similar, just syntactic sugar comes over it.
As I said: promises return values. That's great. But describing callbacks as "worst coding style ever" and "intellectual lazyness" is hyperbole to describe a style that most of the JS community uses.
Also: not sure if you mean 'you' singular or 'you' plural, but personally I use a combination of callbacks for node stuff with basic async patterns applied (not the 'async' module) and promises.
The loader spec hasn't been completed yet so there is no way to load properly modules in es2015, es2016, or es2017 as far as I know.
The hyperbole is not my style actually, but I use it when talking about JS because I've came to realize the in the JS community it's the only way to be heard.
Generally I see the JS async styles continuum as: (0) callbacks < (1) async module < (2) promises < (3) co < (4) real async/await when language supports it, and I advocate skipping the intermediate steps in this continuum, (1) and (3) because they can lead to either badly composable code or subtle bugs. Also, I stressed the "intellectual lazyness" label because I know lots of people learn async module and this, "ok, this is good enough, I'll stop learning new things about writing better async code in JS".
...and I'm ok with good ol' callbacks where it makes sense for performance reasons, like core part of web frameworks or game engines (even if there is no sensible speed reduction, there is a memory overhead from creating a zillion promises).