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.
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. 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.
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.
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).
async getDoc(doc) {
const result = await db.get(doc)
console.log('result:', result)
}// instead of:
function getDoc(doc) {
db.get(doc, result => {
console.log('result:', result)
});
}// or
function getDoc(doc) {
db.get(doc)
.then(result => 'result: ' + result)
.then(::console.log)
.catch(::console.error)
}The second one is that you can await a promise, which turns your 1-level-deep code to 0 levels deep.
1. Promises are a value. They can be passed to other functions. This simplifies a lot of control flow.
2. The `.then` method on a promise is similar to `flatMap` in other languages / libs. The next chain will wait for any nested promises to complete. This flattens callbacks so you can see the flow instead of the pyramid of doom.
3. Native promises include a bit of sugar, such as Promise.all. You need a library to do this with callbacks.
4. Promises are the backbone of async/await. You need them to use it.
Example:
await const [a, b] = Promise.all([getA(), getB()]); const [a, b] = await Promise.all([getA(), getB()]);Controller calls the service.
Service returns a call to a repo with any mapping that needs to occur.
Repo calls out to database or external api and returns the promise.
Makes the work very boring and repetitive for the most part which is a good thing I think.
function someController(req, res) {
UserService.getUser(req.session.user)
.then(SomeService.getSomething)
.then((val) => res.json(val))
.catch(handleError);
}
function getSomething(user) {
return SomeRepo.getSomething(user)
.then(MapSomething)
.catch(alternativeHigherLevelCatch);
}
function getSomething(user) {
return new Promise((resolve, reject) => {
database.getSomething(
'defined parameters',
user,
resolve,
reject
)
});
}You certainly want to call UserService.getUser for many different controllers. Why manually do that for each controller? Simply make a middleware to get the user.
If you take this to its logical conclusion, you end up with small, composable middleware functions, and controllers that are pure functions.
Also agree middleware is a good solution which can go through and provide a user object directly onto the request or another level of abstraction created if you prefer. That was simply to show how multiple service calls could be easily tied together rather than a full and usable example.
A benefit of callbacks though, at least from my experience, is error messages when testing with mocha / chai. I only have experience with jasmine, mocha, and chai but the errors when test driving were much easier to follow with callbacks.
doSomething.then(stepTwo).then(stepThree).catch(errHandler);