Problem?
The problem I have is that my catch has a Promise inside, which means I have nesting. Now, I am strongly against nesting of promises, so I really want to get rid of that, but I just don't see how.
– Flame_Phoenix
The problem is that you think you have a problem – or maybe that you posted this question on StackOverflow instead of CodeReview. The article you linked shows where you adopt a naive view about nested promises
You get a whole bundle of promises nested in eachother:
loadSomething().then(function(something) {
loadAnotherthing().then(function(another) {
DoSomethingOnThem(something, another);
});
});
The reason you’ve done this is because you need to do something with the results of both promises, so you can’t chain them since the then() is only passed the result of the previous return.
The real reason you’ve done this is because you don’t know about the Promise.all()
method.
– Code Monkey, http://taoofcode.net
No, Promise.all
can only sometimes replace nested promises. A simple counter-example – here, one promise's value depends on the the other and so the two must be sequenced
getAuthorByUsername (username) .then (a => getArticlesByAuthorId (a.id))
Nesting promises is not always necessary, but calling it an "anti-pattern" and encouraging people to avoid it before they know the difference is harmful, imo.
statements are not functional
The other linked article shows where you may have been misguided again
Don’t get me wrong, async/await is not the source of all evil in the world. I actually learned to like it after a few months of using it. So, if you feel confortable writing imperative code, learning how to use async/await to manage your asynchronous operations might be a good move.
But if you like promises and you like to learn and apply more and more functional programming principles to your code, you might want to just skip async/await code entirely, stop thinking imperative and move to this new-old paradigm.
– Gabriel Montes
Only this doesn't make any sense. If you look at all of the imperative keywords in JavaScript, you'll notice none of them evaluate to a value. To illustrate what I mean, consider
let total = if (taxIncluded) { total } else { total + (total * tax) }
// SyntaxError: expected expression, got keyword 'if'
Or if we try to use if
in the middle of another expression
makeUser (if (person.name.length === 0) { "anonymous" } else { person.name })
// SyntaxError: expected expression, got keyword 'if'
That's because if
is a statement and it never evaluates to a value – instead, it can only rely on side effects.
if (person.name.length === 0)
makeUser ("anonymous") // <-- side effect
else
makeUser (person.name) // <-- side effect
Below for
never evaluates to a value. Instead it relies on side effects to compute sum
let sum = 0
let numbers = [ 1, 2, 3 ]
for (let n of numbers)
sum = sum + n // <-- side effect
console.log (sum) // 6
The same is true for do
, while
, switch
, even return
and all of the other imperative keywords – they're all statements and therefore rely upon side effects to compute values.
What evaluates to a value then? Expressions evaluate to a value
1 // => 1
5 + 5 // => 10
person.name // => "bobby"
person.name + person.name // => "bobbybobby"
toUpper (person.name) // => "BOBBY"
people .map (p => p.name) // => [ "bobby", "alice" ]
async
and await
are not statements
You can assign an asynchronous function to a variable
const f = async x => ...
Or you can pass an asyncrhonous function as an argument
someFunc (async x => ... )
Even if an async
function returns nothing, async
still guarantees we will receive a Promise value
const f = async () => {}
f () .then (() => console.log ("done"))
// "done"
You can await
a value and assign it to a variable
const items = await getItems () // [ ... ]
Or you can await
a value in another expression
items .concat (await getMoreItems ()) // [ ... ]
It's because async
/await
form expressions that they can be used with functional style. If you are trying to learn functional style and avoid async
and await
, it is only because you've been misguided. If async
and await
were imperative style only, things like this would never be possible
const asyncUnfold = async (f, initState) =>
f ( async (value, nextState) => [ value, ...await asyncUnfold (f, nextState) ]
, async () => []
, initState
)
real example
Here's a practical example where we have a database of records and we wish to perform a recursive look-up, or something...
const data =
{ 0 : [ 1, 2, 3 ]
, 1 : [ 11, 12, 13 ]
, 2 : [ 21, 22, 23 ]
, 3 : [ 31, 32, 33 ]
, 11 : [ 111, 112, 113 ]
, 33 : [ 333 ]
, 333 : [ 3333 ]
}
An asynchronous function Db.getChildren
stands between you and your data. How do you query a node and all of its descendants?
const Empty =
Symbol ()
const traverse = (id) =>
asyncUnfold
( async (next, done, [ id = Empty, ...rest ]) =>
id === Empty
? done ()
: next (id, [ ...await Db.getChildren (id), ...rest ])
, [ id ]
)
traverse (0)
// => Promise [ 0, 1, 11, 111, 112, 113, 12, 13, 2, 21, 22, 23, 3, 31, 32, 33, 333, 3333 ]
A pure program sent from the "heaven of JavaScript developers", to put it in the words of Montes. It's written using a functional expression, errors bubble up accordingly, and we didn't even have to touch .then
.
We could write the same program using imperative style. Or we could write it functional style using .then
too. We can write it all sorts of ways and I guess that's the point – Thanks to async
and await
's ability to form expressions, we can use them in a variety of styles, including functional style.
Run the entire program in your browser below
const asyncUnfold = async (f, init) =>
f ( async (x, acc) => [ x, ...await asyncUnfold (f, acc) ]
, async () => []
, init
)
const Db =
{ getChildren : (id) =>
new Promise (r => setTimeout (r, 100, data [id] || []))
}
const Empty =
Symbol ()
const traverse = (id) =>
asyncUnfold
( async (next, done, [ id = Empty, ...rest ]) =>
id === Empty
? done ()
: next (id, [ ...await Db.getChildren (id), ...rest ])
, [ id ]
)
const data =
{ 0 : [ 1, 2, 3 ]
, 1 : [ 11, 12, 13 ]
, 2 : [ 21, 22, 23 ]
, 3 : [ 31, 32, 33 ]
, 11 : [ 111, 112, 113 ]
, 33 : [ 333 ]
, 333 : [ 3333 ]
}
traverse (0) .then (console.log, console.error)
// => Promise
// ~2 seconds later
// [ 0, 1, 11, 111, 112, 113, 12, 13, 2, 21, 22, 23, 3, 31, 32, 33, 333, 3333 ]