July 30, 2020

Switching from callbacks to promises

I have recently been programming a REST API using the Node.js Express framework. Express starts developers out with callbacks for safe asynchronous functions.

Such as:

// input object
var obj = {
    email: 'user@test.com',
    firstName: 'jane',
    lastName: 'doe',
    favorites:  {
        food: 'pizza',
        animal: 'cat'
    }
}

// business logic
const helpersCB = {
    createFullNameCB: function (firstName, lastName, callback) {
        if (firstName && lastName) {
            const fullName = firstName.concat(' ', lastName);
            callback(null, fullName);
        } else {
            callback('No firstName and lastName!', null);
        }
    }
}

// callback function
function cb(err, data) {
    if (err) {
        console.log(`error: ${err}`);
    }
    console.log(`FullName: ${data}`);
    return data;
}

helpers.createFullNameCB(obj.f, obj.lastName, cb);

Express has been easy to learn and get running quickly, but while writing the API, I encountered situations where I had a callback within callback within callback, a.k.a. callback hell. It is hard to keep track of the scoping of each callback and frustrating to debug errors. After researching this problem, I found other developers with similar issues and the JavaScript Promise API looked like an approach that solved these issues.

Here is the same function as above, but using a promise-based approach.

// promise
const helpersPromise = {
    createFullNamePromise: async function (firstName, lastName) {

        return new Promise((resolve, reject) => {
            if (firstName && lastName) {
                const fullName = firstName.concat(' ', lastName);
                resolve(fullName);
            } else {
                reject('No firstname and lastName!')
            }
        })
    }
}

var fullNamePromise = async () => {
    await helpersPromise.createFullNameP(obj.firstName, obj.lastName)
        .then((result) => {
            console.log(`result: ${result}`);
            return result;
        })
        .catch((error) => {
            console.log(`error creating fullName: ${error}`);
        });
}

Through the resolve/reject pattern, it is easy to reason about what is going on in promise-based functions. No longer do I have to refer outside of the function scope to understand what the function is actually returning or what data it is operating on. I can quickly see the business logic in the resolve/reject and then/catch. Once I started writing promises, I quickly refactored the entire API to use promises. I have seen many benefits including readable, maintainable code for other developers.

I would encourage other Nodejs or Javascript developers to embrace promises and there are great tutorials online, especially the Mozilla learning guide.