Chaining Promises in Javascript

January 09, 2019

We all know about the callback-hell phenomena in Javascript.

Usually in backend processes we are retrieving and comparing data from multiple sources, and our chain of callbacks get deeper and deeper.

Here's an example of one:

this.getUsers()
    .then(users => {
        const allPromises = []

        users.forEach(user => {
            allPromises.push(
                new Promise((resolve, reject) => {
                    db.ref.child(`users/${user.id}/properties`)
                        .fetch()
                        .then(userProperties => resolve(userProperties))
                })
            )

            Promise.all(allPromises)
                .then(userProperties => {
                    const unsoldPropertyIDs = userProperties.filter(uP => uP.status !== "sold")
                        .map(unsoldProperty => unsoldProperty.id)

                    const relatedPropertiesPromises = []
                    unsoldPropertyIDs.map(unsoldPropertyID => {
                        relatedPropertiesPromises.push(
                            new Promise((resolve, reject) => {
                                db.ref.child(`properties/${unsoldPropertyID}/related`)
                                    .fetch()
                                    .then(relatedProperties => resolve(relatedProperties))
                            })
                        )
                    })

                    Promise.all(relatedPropertiesPromises)
                        .then(relatedProperties => console.log("Related Properties", relatedProperties))
                })
        })
    })

A better way of chaining promises would be to return promises at every stage of the way, then you can execute some code in a separate closure.

The trick is to reuse code, and turn most of the functionality into reusable modules. That'll also make the code highly readable and easy to follow or modify.

Now here's a better version:

const getUserProperties = user => (
    new Promise((resolve, reject) => {
        db.ref.child(`users/${user.id}/properties`)
            .fetch()
            .then(userProperties => resolve(userProperties))
    })
)

const getRelatedProperties = propertyID => (
    new Promise((resolve, reject) => {
        db.ref.child(`properties/${propertyID}/related`)
            .fetch()
            .then(relatedProperties => resolve(relatedProperties))
    })
)

this.getUsers()
    .then(users => {
        return Promise.all(
            users.map(user => getUserProperties(user))
        )
    })
    .then(userProperties => {
        return Promise.all(
            userProperties
                .filter(uP => uP.status !== "sold")
                .map(unsoldProperty => getRelatedProperties(unsoldProperty.id))
        )
    })
    .then(relatedProperties => {
        console.log("Related Properties", relatedProperties)
    })