DerickBailey.com

Trade Secrets Of A Developer / Entrepreneur

  •  About
  •  Twitter
  •  G+
  •  RSS
  • Blog
  • Courses
  • Products
  • Newsletter
  • Publications
  • Podcasts
  • Work With Me!

Ending the Nested Tree of Doom with Chained Promises

September 27, 2016 By Derick

Julia Jacobs recently asked a question in the WatchMeCode community slack, about some asynchronous code she wanted to clean up.

In this question, she wanted to know of good options for restructuring deeply nested promises without introducing any new libraries. Would it be possible to clean up the code and only use ES6 features?

Nested callback cleanup

It’s a common pattern and problem – the Nested Tree of Doom – not only with promises, but with JavaScript callbacks in general. 

The full question, is as follows

Hey all. Does anyone know any decent patterns for

a) async ES6 classes other than shoving everything into a native promise in a getter.

b) creating an async waterfall serialization flow with multiple functions using generators.  

I’ll create some gists with samples of the code I’m trying to refactor. I’m trying to stay away from libraries and stick with native ES6 goodness.

When I first read the question without looking at the example code, I wondered if promises were the right way to go.

Personally, I prefer the node style of callbacks as my go-to pattern for asynchronous code. Promises are definitely useful, but they are most useful in specific scenarios.

My initial response, however, was about ES6 generators.

the best way to take advantage of generators and async work is going to be with a small library [like co].

you can write your own with only a few lines of code, though

if you don’t like getters that return promises, can use a method with a callback parameter.

personally, i prefer callback methods over promises, until i have a specific use case for promises (like waiting for multiple things, only wanting to perform the action once but retrieve the value multiple times, chaining async ops, etc)

Once Julia posted her code sample, my advice quickly changed.

While I don’t reach for promises as my first choice, Julia’s scenario and code samples quickly changed my mind. 

Here is what she posted for the code in a hapi.js router:

It’s the result of two weeks of banging out a hapijs arch which parses a huge XML response from a Java API and maps it to a huge json contract with very strict corporate requirements.

Frankly, this code is darn near as beautiful an example as you can find, when it comes to nested callbacks and promises.

And I don’t mean “a beautiful mess” – I mean that Julia has written code that you dream of finding, when you are looking at restructuring.

Most of my own nested promises are garbage – closures; creating promises inside of callbacks; nested .catch and resolve statements and more of a mess than I care to show anyone.

This code is near spotlessly clean, already.

Unlike the mess that I tend to create with nested promises, the code Julia showed us is uniformly written.

It uses no closures.

It takes the results of the previous work and passes it directly to the next work, with nothing else.

And it does all of this through promises that are created by other functions.

The only thing this code really needs, is a small adjustment to remove the nested promises and turn them into chained promises.

As I noted in my response to Julia,

instead of doing `return SearchModel.get(json).then({ …` you can return the promise from `SearchModel.get` directly

`return SearchModel.get(json);`

Promises will take any return value from a `then` callback, and forward it to the next `then` for you

if the return value is a Promise itself, it will wait for that promise to resolve or reject

so the above code should be functionally the same just with less nesting

When you take the first few lines of the above code and apply this idea, it becomes this:

And this is where the magic begins.

By returning the promise from SearchModel.get, the code nesting has been reduced one level.

Apply this same pattern throughout the rest of the sample, and you reduce the nesting by one level, at each level of nesting.

The result is that there are never any nested promises!

But the magic doesn’t end here.

Reducing the nested promises is only the first steps in reorganizing this code.

I continued the example by talking about extracting named functions

if you really want to reduce this code further, extract each of those callbacks into a named function

then you can do `xml.tojsonhttpclient.get().then(transformit).then(mapit).then(…)`

This works because these functions are very clean. They take the result of the previous promise and pass it into the next promise. There’s no closures or other code, as I mentioned before.

The result of extracting the callbacks into named functions looks like this:

Now this is beautiful code!

But if we’re talking ES6, Let’s use ES6.

Shortly after posting this, there was some discussion on twitter, and Richard Livsey pointed out that ES6 arrow functions could be used with implicit returns, instead of extracting named functions.

@derickbailey great post. Another intermediate step could be to use implicit returns vs extracting functions

— Richard Livsey (@rlivsey)

September 27, 2016


With this post, he also provided an example that shows the use of arrow functions to achieve this:

The code I have above may be slightly cleaner when it comes to reading the chained promises, but this is a great option if you’re concerned about lines of code or having all those extra little functions laying around. 

The goal may not be to reduce the number of lines of code, though. 

In this scenario, the goal was to make the code easier to reason about and easier to change, not just reducing the number lines of code (though this may be an important aspect in other scenarios).

By taking what was already clean code and reorganizing it into chained promises instead of nested, the code became easier to follow.

When the functions were extracted and named, though, that’s when the code became easier to understand at a higher workflow level.

You no longer have to dive into the details of each promise’s callback to understand what happens next. Instead, you can look at the high level flow of chained promises and see a simplified name that represents what will happen next.

The code is easier to modify, as well. If you need to insert a new method, change a detail, remove a step, it’s all right there in the high level chaining. 

This type of restructuring is not always going to work, though.

When Julia brought this question to the WatchMeCode community slack, she was already in a good place. 

Most of the time when I’m looking at nested promises, I’m in much worse shape. 

If you face code that has closures around variables, re-using them between promise callbacks, for example, you’re going to run into problems.

If you have nested promises being created within the promise callbacks directly, or worse, you have nested promise chains being resolved and rejected, it can be incredibly difficult to fix.

You may find yourself in a situation where a promise chain is simply the wrong way to solve the problem.

But if you’re looking at code which is clean and concise like what Julia brought to us, or if you can move your code from where it is, to this clean and uniform state, then you should be able to take full advantage of chained promises.

Tweet

 


 

Related Post

Docker for JavaScript Developers: On-Site Training
3 Features of ES7 (and Beyond) That You Should Be ...
How a 650MB Node.js Image for Docker Uses Less Spa...
3 Rules For When A New JavaScript Feature Is Ready...
Never Use The :latest Image From Docker Hub

Filed Under: Callbacks, Community, ES6, JavaScript, NodeJS, Patterns, Promises, WatchMeCode

About Derick

Derick Bailey is a developer, entrepreneur, author, speaker and technology leader in central Texas (north of Austin). He's been a professional developer since the late 90’s, and has been writing code since the late 80’s. Derick has built software for organizations of all shapes and sizes, including contributions to Microsoft's MDSN library, running several very highly regarded open source projects, creating software solutions for large financial organizations, healthcare orgnaizations, world-class airlines, the U.S. government, and more. These days, Derick spends most of his time working on content for his own entrepreneurial efforts at WatchMeCode.net, playing video games when he gets a chance, and writing code for for his few remaining clients. You can reach Derick at DerickBailey.com or on twitter, @derickbailey.

Derick Bailey Around The Web

  • Twitter: @derickbailey
  • Google+: DerickBailey
  • Screencasts: WatchMeCode.net
  • eBook: Building Backbone Plugins

Copyright © 2016 Muted Solutions, LLC. All Rights Reserved · Log in