Advertisement
  1. Code
  2. Node.js
Code

JavaScript Callbacks, Promises, and Async Functions: Part 2

by
Difficulty:AdvancedLength:ShortLanguages:
This post is part of a series called JavaScript Callbacks, Promises and Async Functions.
JavaScript Callbacks, Promises, and Async Functions: Part 1

Introduction

In part one of this tutorial, we learned the principles behind asynchronous programming and using callbacks. To review, asynchronous programming allows us to write code that is non-blocking by executing blocking tasks later. Callback functions provide a way to synchronize the execution of our code. 

However, nesting callbacks repeatedly is not a good pattern to follow. Here come promises to the rescue. Promises have been used for a while in JavaScript libraries, but now you can use them natively in your code. Async functions improve upon promises by allowing us to write our tasks one after the other without us having to worry about the timing of their execution.

Contents

  • Promises
  • Async Functions
  • Review
  • Resources

Promises

Let’s take a look at what a promise is conceptually. Imagine the scenario where you are shopping online and you purchase a pair of shoes. When you check out, you are emailed a summary of your purchase. 

This order confirmation is like a promise. The promise is your guarantee that you will get something back later from the company. While your order is pending, your life does not stop, of course. You continue doing other tasks like surfing the internet. If your order is fulfilled, you will receive an email with the shipping info. It is possible that your order is rejected. The item you ordered may be out of stock, or there could be an issue with your payment method. In this case, you would get an email telling you about the error.

In code speak, a promise is an object that ensures we will get a future value for our request whether it succeeds or fails. This is the general form for creating and using a promise:

To create a promise, you instantiate a promise object and write your asynchronous code inside the promise's callback function. The data you want returned from the promise is passed as an argument to the resolve function, and your error message is passed into the reject function. We chain together promises using the then method. This lets us execute the tasks sequentially. 

If we need to pass the results of a task to the next task, we return it in the then method. We may want to chain promises together when we are interested in transforming values or we need to execute our code in a particular order. At the end of the chain, we catch our errors. If an error occurs in any of our tasks, the remaining tasks are skipped, and the error is sent to our catch block.

In part one of this tutorial, we used callbacks to open a file and retrieve a post and its comments. This is what the complete code looks like using promises:

index.js

The difference here is that our method to open the file is now wrapped in a promise object. And instead of nesting our tasks with callbacks, they are chained together with then

As you can see, we haven’t eliminated the need for callbacks. We are just using them differently. Before, we nested our callbacks so that we could continue the execution of our code in the next task. 

This reminds me of when I call customer service about a problem and instead of the agent resolving my problem, I get transferred to someone else. That someone else may or may not resolve the call, but as far as the first agent is concerned, it is someone else’s responsibility. 

With promises, we will get something back before going to the next task. If we need to carry that something to the next continuation of our code, we can use a then statement.

Task

Using promises, write a program that will open a file of users, get the user’s info, and then open a file of posts and print all of the user's posts.

Async Functions

Promises are an improvement in the design of our program, but we can do better. It would be very convenient if we could execute our tasks synchronously like this:

Well, we can with the async/await pattern. To do this, we start by wrapping our tasks in an async function. This function returns a promise. Then we implement error handling by wrapping our tasks inside a try/catch statement. 

If the promise is fulfilled, it will complete whatever tasks were inside of our try block. If it is rejected, the catch block will be executed. Adding the await keyword before any task pauses our program until the task completes. 

This is the general form for using async functions:

Using this pattern, we can rewrite how we execute our code in our files example.

I like structuring our async code with a try/catch statement because it clearly separates error handling code from regular code. If any of the code in our try block causes an error, it will be handled by the catch block. Plus, we can add a finally block that will execute code regardless of whether our tasks succeed or fail. 

One example for using this pattern is when we have cleanup code we need to execute. This code doesn’t necessarily have to be contained in a finally block. It can be written after the catch statement, and it will execute. Promises do not have this syntax built in. We would have to chain another then statement after our catch statement to achieve the same effect.

Task

Using async/await, write a program that will open a file of users, get the user’s info, and then open a file of posts and print the user's info and all of their posts.

Review

Callbacks aren’t inherently evil. Passing functions into other functions is a useful pattern in JavaScript. Callbacks become a problem when we are using them to control the flow of our application logic. Since JavaScript is asynchronous, we have to take care how we write our code because tasks won’t necessarily finish in the order they were written. That is not a bad thing because we don’t want any task blocking the continuation of the program. 

Promises are a better pattern for writing asynchronous code. They don’t just fix the clutter of nested callbacks, but they also keep control of the outcome of a task within the task. Passing control to another task, whether that task is our own code or a third-party API, makes our code less reliable. Lastly, async functions allow us to write our code synchronously, which is much more intuitive and easier to understand.

Resources

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.