4 ms·
Folding Promises in JavaScript
- egeozcan 9y agoI 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.
- wereHamster 9y agoThe pair of functions form an isomorphism. You have these two laws: forall x. objToArray(arrayToObj(x)) == x forall x. arrayToObj(objToArray(x)) == x
- paavohtl 9y agoIt'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 9y agoEDIT: 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…
- idbehold 9y agoYeah, 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' }
- fair24 9y ago> 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 9y agoI 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.
- jacobr 9y agoI feel the code I wrote while on this bandwagon is the hardest to understand for others and for me myself today. pullAllBy(pluck(things, 'bar').map(compose(xor, lol, rofl)).reduce(differenceWith('id')) Just write your transformations inline and go work on the next feature.
- Androider 9y agoThe trick is to break that (on the dots) into three const assignments with descriptive names. Practically self-documenting, easily debuggable. If you don't use the builtin transformations, you'll just end up re-implementing them, poorly. And adding to the cognitive overhead with new concepts. And I have to read your code with a fine-toothed comb to ensure it's really side-effect free. I don't advocate turning everything into a named function as in your example though, short one-off functions should all be inline IMO.
- disantlor 9y agothe nice thing is that you can easily split that line up as much as makes sense, should you decide you need to access some intermediate form of the data. and you can use the variable names as a comment that explains what that chunk of transformations represents. so someone reading over it can kind of skim down the left side and follow what's happening and scan to the right if they need to understand some part in detail
- porlune 9y agoThe 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).
- CapacitorSet 9y agoI 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?
- lsjroberts 9y agoI 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).
- catnaroek 9y agoThe other way around. A homomorphism is a structure-preserving map between two arbitrary objects, whereas an endomorphism is a homomorphism where the source and target objects coincide.
- tel 9y agoEndomorphism has less implied structure. Lots of dumb things are endomorphisms. Homomorphism implies "structure preservation" which can make it more specific.
- catnaroek 9y agoI'm surprised to read this coming from you.
- dllthomas 9y agoIt seems "endomorphism" is used both ways (in, presumably, different contexts). https://ncatlab.org/nlab/show/endomorphism https://ncatlab.org/nlab/show/endomorphism I think "endomorphism is a homomorphism ..." is more common, but notably is not the usage in Haskell (https://hackage.haskell.org/package/base/docs/Data-Monoid.html#t:Endo https://hackage.haskell.org/package/base/docs/Data-Monoid.ht...)
- adamjc 9y ago>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 9y agoSame 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 :)
- YCode 9y agoI suppose this might be useful in situations where you are querying an API(s) with multiple requests and some will certainly return seconds before others. This way you could have the same reducer handle the results and begin updating the UI as the results come in. An example real-world app might be a price comparison tool or social media aggregator.
- naasking 9y ago> I suppose this might be useful in situations where you are querying an API(s) with multiple requests and some will certainly return seconds before others. But it's still a serialized operation so the parallelism is still limited. What's really needed is a "parallel reduce" using something like C's select function that will reduce in an arbitrary order using any promises that are ready at any given step.
- codefined 9y agolet accumulator = 0 for (let item of array) { const value = await item // your code here } Is identical, doesn't use 'cool' reduce features but is much easier to read in my opinion.
- noelwelsh 9y agoI 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.
- molf 9y agoWith 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() }
- megawatthours 9y agoThat code does the same thing as https://news.ycombinator.com/item?id=15302465 https://news.ycombinator.com/item?id=15302465 but not the same thing as the code in the article.
- fortythirteen 9y ago"Programs must be written for people to read, and only incidentally for machines to execute." - Harold Abelson
- minitech 9y agoWith async (it’s just monads!): listOfPromises.reduce( async (m, n) => await m + await n, 0, )
- chajath 9y agoI've written a javascript library to deal with folding and mapping recurring promises (i.e. promises that resolve to a value part of which contains clue to the "next" promise) https://github.com/google/chained-promise https://github.com/google/chained-promise