Using Promises In The Twilio Module For Node.js - Twilio
Power modern communications. Build the next generation of voice and SMS applications.
Start building for free

Using Promises In The Twilio Module For Node.js

KevinWhinnery

Promises Twilio Module Node JS

Editor’s note: Are you new to using Twilio with node.js?  If so, you might want to check out this introductory blog post first.  You can also find the reference documentation for the Twilio Node module here.  Know what you’re doing?  Just npm install --save twilio to get started :)

Async Programming in node.js

One of the core design goals of node.js is to prevent blocking operations wherever possible.  This results in many of Node’s core system modules providing asynchronous interfaces that take a callback function. That function will be invoked at some point in the future when a requested operation completes, as in the  “readFile” function in Node’s file system module:

var fs = require('fs');

fs.readFile('/etc/passwd', function (err, data) {
    if (err) throw err;
    console.log(data);
});

This callback function accepts two arguments – the first is an error object, which contains any error that occurred during execution of the task.  The second argument is the actual data returned from the API.  This callback style and signature is common across core Node, and in many popular modules in userland.  If you haven’t already, check out Isaac’s slides from TxJS 2013 which do a good job explaining why callbacks are standard for many APIs in Node.  You might also enjoy Isaac’s piece on designing asynchronous APIs.  In short: do not release Zalgo.

The Twilio module was originally designed with only this callback interface:

var twilio = require('twilio');
var client = twilio('ACCOUNT_SID', 'AUTH_TOKEN');

client.sendMessage({
    to:'+16518675309',
    from:'+16515556789',
    body:'Jenny! Please give me another chance! <3'
}, function(err, message) {
    if (err) {
        console.error('Text failed because: '+err.message);
    } else {
        console.log('Text sent! Message SID: '+message.sid);
    }
});

This works great if you’re only making a single request to Twilio. But if you’re going to be making multiple requests, this callback interface could potentially cause your code to march out quickly to the right, forming the dreaded pyramid of doom:

var twilio = require('twilio');
var client = twilio('ACCOUNT_SID', 'AUTH_TOKEN');

// See if we have any phone numbers in the 651 area code...
client.incomingPhoneNumbers.list({
    phoneNumber:'+1651*******'
}, function(listError, listResults) {
    if (listError) {
        console.error('Ruh roh - couldn\'t list numbers because: '+listError.message);
    } else {
        // Check if we have any...
        if (listResults.incomingPhoneNumbers.length === 0) {
            // Buy a US phone number in the 651 area code using callbacks
            client.availablePhoneNumbers('US').local.get({
                areaCode:'651'
            }, function(searchError, searchResults) {

                // handle the case where there are no numbers found
                if (searchResults.availablePhoneNumbers.length < 1) {
                    console.error('Oh noes! There are no 651 phone numbers!');
                } else {

                    // Buy the first phone number we found
                    client.incomingPhoneNumbers.create({
                        phoneNumber:searchResults.availablePhoneNumbers[0].phoneNumber,
                        voiceUrl:'https://demo.twilio.com/welcome/voice',
                        smsUrl:'https://demo.twilio.com/welcome/sms/reply'
                    }, function(buyError, number) {
                        if (buyError) {
                            console.error('Buying the number failed. Reason: '+buyError.message);
                        } else {
                            console.log('Number purchased! Phone number is: '+number.phoneNumber);
                        }
                    });
                }

            });
        }
    }
});

Promises, Promises

One cure (but not the only one) for the pyramid of doom is to use promises rather than callbacks for asynchronous code.  If you’re not familiar with promises, the folks over at StrongLoop have a great resource on their blog to help you wrap your head around the concept.

Promises have many other benefits beyond preventing the pyramid of doom, however. Another big bonus is that we can pass a promise value around within our application, and other parts of our program can access the result of an async call, whether or not an async operation has actually been completed. For more on why and when you might use promises, read through the StrongLoop post and the README for the Q library.

Promises in the Twilio Module

As of the 1.4.0 release of the twilio module on npm, there is now an optional promise interface you can use to consume data from the API.  The original callback interface is still 100% supported, and always will be.  The difference is that for all REST client requests, the twilio module now returns a promise (using the de facto node.js standard promise library Q), which you can use how you see fit.

Here’s a simple example of how you could make a phone call with the twilio module using the new promises interface:

var twilio = require('twilio');
var client = new twilio.RestClient('ACCOUNT_SID', 'AUTH_TOKEN');

// A simple example of making a phone call using promises
var promise = client.makeCall({
    to:'+16515556667777', // a number to call
    from:'+16518889999', // a Twilio number you own
    url:'https://demo.twilio.com/welcome/voice' // A URL containing TwiML instructions for the call
});

// You can assign functions to be called, at any time, after the request to
// Twilio has been completed.  The first function is called when the request
// succeeds, the second if there was an error.
promise.then(function(call) {
    console.log('Call success! Call SID: '+call.sid);
}, function(error) {
    console.error('Call failed!  Reason: '+error.message);
});

In simple cases like this one, the benefits of using a promise are not obvious. In fact, for single calls to the Twilio API (like shooting off a text message or making a phone call), the callback interface is probably an easier mechanism to use. However, the benefits of using promises quickly become apparent for more complex use cases. Let’s revisit that nasty bit of Twilio REST API access code we wrote earlier to see how promises might make it a little nicer:

var twilio = require('twilio');
var client = twilio('ACCOUNT_SID', 'AUTH_TOKEN');

client.incomingPhoneNumbers.list({
    phoneNumber:'+1651*******'
}).then(function(results) {
    if (listError) {
        throw { message: 'Ruh roh - couldn\'t list numbers because: '+listError.message };
    }

    // Return promise for the next call to Twilio...
    return client.availablePhoneNumbers('US').local.get({
        areaCode:'651'
    })
}).then(function(searchResults) {
    // handle the case where there are no numbers found
    if (searchResults.availablePhoneNumbers.length < 1) {
        throw { message: 'Oh noes! There are no 651 phone numbers!' };
    }

    // Return promise for the call to buy a number...
    return client.incomingPhoneNumbers.create({
        phoneNumber:searchResults.availablePhoneNumbers[0].phoneNumber,
        voiceUrl:'https://demo.twilio.com/welcome/voice',
        smsUrl:'https://demo.twilio.com/welcome/sms/reply'
    });
}).then(function(number) {
    // Success!  This is our final state
    console.log('Number purchased! Phone number is: '+number.phoneNumber);
}).fail(function(error) {
    // Handle any error from any of the steps...
    console.error('Buying the number failed. Reason: '+error.message);
}).fin(function() {
    // This optional function is *always* called last, after all other callbacks
    // are invoked.  It's like the "finally" block of a try/catch
    console.log('Call buying process complete.');
});

It might be a little longer in terms of lines of code, but it has advantages in readability. We’ve successfully flattened the pyramid, so our code now reads top to bottom instead of left to right AND top to bottom. We can consolidate our error handling logic to trigger when any of the three API calls fail (if we want). We can also easily define a function that will execute, no matter what, when all the async calls are complete.

Conclusion

As of twilio-node 1.4.0, promises (in addition to callbacks) are supported for handling asynchronous calls to the Twilio API. Promises can be useful for flattening the pyramid of doom, and for passing the results of an async operation around within an application.

If you’re just making a single call to Twilio, the callback interface is probably your best bet. If you need to pass the results of an async call around, or need to make multiple API requests, the promises interface might make sense. Either is totally fine and supported by the module.

Let us know what you think about using promises in the twilio module for Node.js at @kevinwhinnery

Sign up and start building
Not ready yet? Talk to an expert.