Breaking and taming the Promise chain in JS

You might need to resort to workarounds that make your code more complex and less readable.

There is a better way to implement Promise chains.

Let’s take a piece of ugly, old-school code and improve on that.

Driving a car, the wrong wayTake a look at this code (…I dare you!):'use strict'function doAction(action) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`${action}: done.

`) }, 1000) })}console.

log('Getting ready to drive the car.

')// Welcome to the "callback hell", the place where// all bad programmers go ;)// This is an example of how NOT to write JS code.

var driveCar = doAction('Unlock the car') .

then(result => { console.

log(result) return doAction('Open the car door') .

then(result => { console.

log(result) return doAction('Get on board') .

then(result => { console.

log(result) return doAction('Insert key') .

then(result => { console.

log(result) return doAction('Turn key') .

then(result => { console.

log(result) return doAction('Release brake') .

then(result => { console.

log(result) return doAction('Engaging gear') .

then(result => { console.

log(result) console.

log('Godspeed!') }) }) }) }) }) }) })It looks horrible, messy and is hardly maintainable.

A slightly better approachThe same code, rewritten usingPromise.

all():'use strict'function doAction(action) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`${action}: done.

`) }, 1000) })}console.

log('Getting ready to ride the car.

')var unlockCar = doAction('Unlock the car')var openCarDoor = doAction('Open the car door')var getOnBoard = doAction('Get on board')var insertKey = doAction('Insert key')var turnKey = doAction('Turn key')var releaseBrake = doAction('Release brake')var engageGear = doAction('Engaging gear')Promise.

all([ unlockCar, openCarDoor, getOnBoard, insertKey, turnKey, releaseBrake, engageGear]).

then(result => { console.

log(result) console.

log('Godspeed!')})This look definitely better, it is easier to read and maintain.

The problem with this code is that the results returned from each Promise become available when all of the Promises in the chain have been resolved.

As I mentioned in the introduction, there are ways to work around that:'use strict'// We will use this variable to store return values// while the Promises in the chain are resolvedvar results = [];function doAction(action) { return new Promise((resolve, reject) => { setTimeout(() => { console.

log(results) // Updated at each invocation results.

push(`${action}: done.

`) resolve() }, 1000) })}console.

log('Getting ready to ride the car.

')var unlockCar = doAction('Unlock the car')var openCarDoor = doAction('Open the car door')var getOnBoard = doAction('Get on board')var insertKey = doAction('Insert key')var turnKey = doAction('Turn key')var releaseBrake = doAction('Release brake')var engageGear = doAction('Engaging gear')Promise.

all([ unlockCar, openCarDoor, getOnBoard, insertKey, turnKey, releaseBrake, engageGear]).

then(result => { console.

log('Godspeed!')})This is a common approach, but I don’t like it either.

Storing all return values in an array is error-prone, should for example the order of invocation of the Promises change.

I could define individual variables for each return value and then populate them one by one, but this code would still look untidy to me:var result1;var result2;var result3;.

function promisifiedFunction1() { // Set result1}function promisifiedFunction2() { // Use result1 and set result2}function promisifiedFunction3() { // Use result2 and set result3}.

Promise.

all([ promisifiedFunction1(), promisifiedFunction2(), promisifiedFunction3(), .

]).

then(result => { console.

log('Completed!')})Finally taming the Promise chainThis is my favourite approach to individually invoke each Promise, keeping the code tidy and passing return values to the next Promise:'use strict'function doAction(action) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`${action}: done.

`) }, 1000) })}console.

log('Getting ready to drive the car.

')var unlockCar = doAction('Unlock the car')var openCarDoor = unlockCar.

then(result => { console.

log(result) // Result from unlockCar return doAction('Open the car door')})var getOnBoard = openCarDoor.

then(result => { console.

log(result) // Result from openCarDoor return doAction('Get on board')})var insertKey = getOnBoard.

then(result => { console.

log(result) // Result from getOnBoard return doAction('Insert key')})var turnKey = insertKey.

then(result => { console.

log(result) // Result from insertKey return doAction('Turn key')})var releaseBrake = turnKey.

then(result => { console.

log(result) // Result from turnKey return doAction('Release brake')})var engageGear = releaseBrake.

then(result => { console.

log(result) // Result from releaseBrake return doAction('Engaging gear')})engageGear.

then(result => { console.

log(result) // Result from engageGear console.

log('Godspeed!')})Done!Source code for this article is available on GitHub.

.

. More details

Leave a Reply