DEV Community

loading...

Pro tip: using Promise.then for function composition

shalvah profile image Shalvah Originally published at blog.shalvah.me Updated on ・1 min read

I've become a fan of functional programming in recent times (even though I still don't know much about it). Over time, I've developed a habit when working with Promises in JavaScript. I try to construct my code so it ends up reading like this:

let tweetsToAttendTo = fetchTweets()
  .then(removeDuplicates)
  .then(removeTweetsIveRepliedTo)
  .then(fetchRelevantTweets);
Enter fullscreen mode Exit fullscreen mode

This means I'm using then not only to control flow but also to pipe output of one function to the input of another (thereby creating a pipeline). I find that this pattern makes the code easier to read and reason about, and gets rid of unnecessary filler like the arrow function and extra variable in the following:

fetchTweets()
  .then(tweets => removeDuplicates(tweets));
Enter fullscreen mode Exit fullscreen mode

Be careful though! There are a few things you must know when using this pattern:

  1. Applying this method depends on the result of the previous function being the input to the next function
  2. Be careful when using object methods. For instance, the following two snippets of code aren't the same thing:
getModels().then(r => manager.applyFilters(r))

// any calls to `this` in `manager.applyFilters` will return undefined
getModels().then(manager.applyFilters)
Enter fullscreen mode Exit fullscreen mode

Ultimately, don't force it. I use this often, but if it just isn't working (output of F1 isn't input of F2, F2 behaves differently, I need to do special error handling), I let it go. Remember, no silver bullets!

Discussion (13)

pic
Editor guide
Collapse
jreina profile image
Johnny Reina

The cool thing about Promises is that the then method, when used this way, is just like the map function. It has the same abstract type signature. This makes Promises behave like any other functor, which means that this method respects composition. Therefore,

somePromise
  .then(f)
  .then(g)
  .then(h);

is the same thing as

const w = pipe(f, g, h);
somePromise.then(w);
Collapse
jhlagado profile image
John Hardy

Yes, I was thinking that as well. When chaining a sequence of maps, it's better to only map once using a composition of functions.

Collapse
shalvah profile image
Shalvah Author

I'm curious as to why you think it's better to "map once". I personally prefer using "then" repeatedly, because it might take a while for people who aren't familiar with functional programming to grab the concept of piping.

Collapse
tunaxor profile image
Angel D. Munoz • Edited

YES! I wish more people started to use similar patterns to this usage, but I still see a lot of deferreds and new Promise() for standard promise usage
Quite a good read Shalvah A!

as a side note:

I remember finally looking at the light with promises after reading several articles, but the one which clicked on me was this

pouchdb.com/2015/05/18/we-have-a-p...

In it includes one example of your usage, which in turn is part of a quiz in promises, ultra recommended lecture if someone doesn't understand promises

Collapse
justsml profile image
Daniel Levy

@shalvah this article is fantastic! I've been exploring the ergonomics of the pattern (particularly around promises) for a little while now... I wrote up an example-rich github project to outline the refactor process: github.com/justsml/escape-from-cal...

@Angel, that pouchdb article is amazing, I make all my students read it :D

Collapse
pichardoj profile image
J. Pichardo

The only issue I see here is that promise is always asynchronous and using then for function chaining might compromise shared data.

Collapse
shalvah profile image
Shalvah Author

I'm not sure I understand you. Could you give an example of what you mean by "compromising shared data"?

Collapse
pichardoj profile image
J. Pichardo

I mean that each time you chain a function using then that function is added to the queue which means that it will wait for at least a tick to be called, my only concern is if during that tick a variable, that one of the callbacks relies on, is modified. Maybe is not a pressing matter, just a concern I have.

Nice post tho.

Thread Thread
justsml profile image
Daniel Levy

@Pichardo Promises resolve only once, and their value is immutable. And in a functional promise chains each step results in a read-only state.

Collapse
bgadrian profile image
Adrian B.G.

I've done a couple of functions like this, but still I haven't solved the problems of

  • testing it
  • how do you find out where exactly crashed on production and with what input?
Collapse
shalvah profile image
Shalvah Author
  • Testing: I don't think this is particularly a problem. You test them the way you test regular functions. Write unit tests for the individual functions and write a test for the entire pipeline.
  • Error handling: You can intersperse a couple of .catch blocks in there to handle specific errors during execution. But the problem would be when you need to exit the chain if an error occurs midway through. In such a case, I don't recommend using this approach, as that would lead to a lot of crazyyyy code. For uncaught errors, the stack trace would typically contain the name of the function that failed.

I hope I've addressed your concerns. Did I miss something?

Collapse
ashinzekene profile image
Ashinze Ekene • Edited

It's called tacit programming or point free. I love doing it too

Collapse
shalvah profile image
Shalvah Author

Right. I recall seeing that in a book on FP. Limiting the number of points.