top | item 15301785

Folding Promises in JavaScript

60 points| anon335dtzbvc | 8 years ago |codementor.io

47 comments

order
[+] adamjc|8 years ago|reply
>How can we make it better ? Let's start by removing the requirement for identity value to always be the promise.

I challenge the view that making the identity value being able to be something other than a Promise is 'making it better'. Pointless abstraction is one of my pet peeves in this industry. This looks like it has gone from a fairly straightforward, if kludgy, piece of code to something far more complex. Why not just:

  const listOfPromises = [...]

  const result = Promise.all(listOfPromises).then(results => {
    return results.reduce((acc, next) => acc + next)
  })


?
[+] megawatthours|8 years ago|reply
Same reason given in the bluebird library documentation:

> Promise.reduce will start calling the reducer as soon as possible, this is why you might want to use it over Promise.all (which awaits for the entire array before you can call Array#reduce on it).

Whether this is ever necessary is another matter :)

[+] charliesome|8 years ago|reply
> Pointless abstraction is one of my pet peeves in this industry. This looks like it has gone from a fairly straightforward, if kludgy, piece of code to something far more complex. Why not just: [code]

Your example code works just fine for promises of course, but not all monads support a coalescing operation like Promise.all.

So even though this article only discusses folding over Promises, the core idea here can be generalised to any monad type (such as Promise, Result, Option, or anything else)

[+] noelwelsh|8 years ago|reply
I don't think this is very well written. It doesn't start with any motivating problem, it introduces terms (functor) without defining them, and a lot of what is discussed doesn't apply to solving the problem.
[+] fair24|8 years ago|reply
> Folding Promises in JavaScript

Or: How to make simple things complex and make a codebase a complete puzzle for those that come after you?

[+] Androider|8 years ago|reply
I feel nothing has improved code readability like the recent mainstreaming of map/filter/fold/reduce and "const all the things". This type of code is so easy to follow, reason about and trivial to debug at every step, once you internalize the few primitive functions.

I don't think you need to necessarily memorize these transformation names, but writing these types of functions is all I seem to be doing these days, transforming one thing into another line for line.

[+] jackcviers3|8 years ago|reply
These things map/reduce/compose/flatmap are universal - they're in java, python, c#, Ocaml, Haskell, lisp, ruby, javascript...

Complaining about learning them is like complaining about for loops. They just exist.

Just because some are more familiar with for loops than map doesn't mean that more universal, immutable, expression - based solutions are not widely familiar and easy to understand to programmers coming from other languages.

Readability is subjective.

[+] brokenpromise|8 years ago|reply
I've been working with JavaScript since February and I want to give you a hug.
[+] CapacitorSet|8 years ago|reply
I can't quite understand the difference between endomorphism ("input and output of the transformer must be from the same category") and homomorphism ("structure preserving transformation. We always stay in the same category"). Can someone help?
[+] mmarx|8 years ago|reply
Homomorphisms are structure-preserving mappings between different types (called “category” in the post, in usual terminology, these would be »objects« in a »category«, though). Endomorphisms are special homomorphisms, mapping from a single type (»object«) to the same type (»object«).

It is indeed very unfortunate that the article conflates terminology.

[+] lsjroberts|8 years ago|reply
I believe homomorphism is a subset of endomorphism.

So a function that turns an array into another array of different length would be endomorphic (since it maintains the same type), but not homomorphic since it has a different structure (a different set of keys).

[+] tel|8 years ago|reply
Endomorphism has less implied structure. Lots of dumb things are endomorphisms. Homomorphism implies "structure preservation" which can make it more specific.
[+] molf|8 years ago|reply
With async/await this can become:

    const reduceP = async (fn, identity, listP) => {
      const values = await Promise.all(listP)
      return values.reduce(fn, identity)
    }
The whole thing feels like a synthetic and overcomplicated example, though. In practice I'm sure I'd just write:

    let total = 0
    while (listP.length > 0) {
      total += await listP.pop()
    }
[+] egeozcan|8 years ago|reply
I don't know much about these concepts but isn't `const objToArray = ({ a }) => [a];` losing data, that being the key of the value in the object? I'm asking because it says that "Isomorphism is a pair of transformations between two categories with no data loss".

In any case, this is very helpful, thanks for writing/sharing.

[+] paavohtl|8 years ago|reply
It's a pair of transformations between [A] and { a: A }, not between arbitrary arrays and objects.

As long as you know what the transformation is, you can convert between them without data loss.

[+] navaati|8 years ago|reply
EDIT: see paavohtl's comment: I hadn't payed attention to the types, dumb me.

You're right, because for a pair of functions f and g, you have an isomorphism if:

  f(g(x)) == x

  g(f(x)) == x
for every x. However, here of course

  (([a]) => {a})( (({ a }) => [a])({ key: 'data'}) )
is equal to

  { a: 'data' }
The OP doesn't quite master what he's talking about…
[+] wereHamster|8 years ago|reply
The pair of functions form an isomorphism. You have these two laws:

    forall x. objToArray(arrayToObj(x)) == x
    forall x. arrayToObj(objToArray(x)) == x
[+] idbehold|8 years ago|reply
Yeah, that was my though as well. It seems like what you need is something like:

   const objToArray = Object.entries
   const arrayToObj = (a) => a.reduce((a, [k, v]) => ((a[k]=v), a), {})
   arrayToObj(objToArray({ foo: 'bar' })) // { foo: 'bar' }
[+] fortythirteen|8 years ago|reply
"Programs must be written for people to read, and only incidentally for machines to execute." - Harold Abelson
[+] porlune|8 years ago|reply
The author mentions the library Bluebird, which I think is a fantastic library. The 'mapSeries' method it offers is also very useful when iterating over an array of values that need to be 'promisified' and mapped in the given order. You can even set 'concurrency' as an option, which puts a limit on the concurrent promises that can run (great for reducing API load).
[+] minitech|8 years ago|reply
With async (it’s just monads!):

  listOfPromises.reduce(
    async (m, n) => await m + await n,
    0,
  )