Polish your asynchronous code with Promises in Node.js

Promises-in-Nodejs-new-ratio.jpg

Take advantage of promises in our Node.js library for graceful asynchronous programming and refined serverless implementation on Channels.

Introduction

Promises are now supported in our Node.js library.

This has a lot of value for users of the SDK, especially those using serverless platforms such as AWS Lambda or Vercel. Advantages of using the Promise object when writing JavaScript apps include:

  • Less spaghetti code
  • An escape from callback hell
  • Easier serverless implementation for AWS Lambda and Vercel

What do JavaScript Promises do?

A Promise is a proxy for a value which is not necessarily known when a function is created but will eventually become available. This allows you to associate a handler with an asynchronous action’s eventual success value or failure reason. Rather than returning the final value immediately, the method promises to supply the value at some point in the future, and defers further actions until that action has completed or responds to its failure.

Handling asynchronous JavaScript gracefully can be fairly hard to get right. Using the Promise function is a good way to avoid getting stuck in callback hell.

What is callback hell?

The complexity of multiple nested callbacks means JavaScript code can end up looking obscure and being difficult to follow. When every callback takes an argument that is a result of the previous, the code takes on a structure which is tricky to read and maintain (see below). An error in one function will also affect all others.

1doSomething(param1, param2, function (err, paramx) {
2    somethingElse(paramx, function (err, result) {
3        anotherThing(function (err, result) {
4            doMoreStuff(param3, function (err, result) {
5                oneMoreOperation(function(x) {
6                    console.log(result)
7                });
8            });
9        });
10    });
11});

The structure, and army of }); at the end (*shiver*) is what is affectionately known as the pyramid of doom, characteristic of callback hell.

Promises keep your code shallow. Your async code will still appear as though it is executing in a top-down way making it much easier to read, and can handle more types of errors due to try/catch error handling.

Refining serverless implementation

Serverless runtimes are particularly good candidates for Pusher integration, because you are tackling large chunks of data which are captured from multiple sources and being processed in a realtime way. Ephemeral runtime however, doesn’t lend itself well to stateful long-lived connections. Application logic in serverless architectures is often powered by short-lived execution environments.

Luckily serverless can easily be integrated with 3rd party services, meaning that persistent connections, such as WebSockets, are easily handled by a separate component (like Pusher Channels) in order to avoid hitting execution limits.

This new update to the Node.js SDK helps with serverless implementation. In the previous library using a serverless environment like AWS Lambda could leave you with some hacky async coding to do in order to avoid prematurely ending execution when using asynchronous handlers.

For example, we might try to write a basic “hello world” AWS Lambda function like:

1exports.handler = async function () {
2  // This won't work!
3  pusher.trigger("foo", "bar", "hello")
4}

Since the trigger method is asynchronous, the function will often exit before the message has been sent to Pusher. Until recently, there were two ways to fix this:

  • Use old-style, non-async handlers, where function execution continues until the event loop is empty. You might be using async handlers for other reasons so this isn’t an option for everyone, or
  • Wrap the trigger call in a promise, and await it.
1exports.handler = async function () {
2  await new Promise((resolve, reject) =>
3    pusher.trigger("foo", "bar", "hello", (err, req, res) =>
4      err ? reject(err) : resolve(res)
5    )
6  )
7}

While this worked, it required a lot of boilerplate. As of Node.js SDK v4.0.0 the trigger method returns a promise, so you can await the trigger directly, which is a lot faster and more simple!

1exports.handler = async function () {
2  await pusher.trigger("foo", "bar", "hello")
3}

For full details of everything included in the new Node.js library release, and to start using promises in your Node.js app, check our changelog.

You can sign up to start building realtime for your serverless application with Pusher Channels for free today.