Patterns for Dealing with Async Code

We will use setTimeout to simulate an asynchronous call to a database and async network requests. Consider the following scenario:

  • We look up a user in our database to collect its bank account number.
  • We send an HTTP GET request to a bank, providing the account number, to receive a list of loans associated with the given account.
  • After the loan list is received, we will send a second request to get a complete transaction history (e.g., payments, etc.) associated with the most recent loan.

Don't get hung up on irrelevant details such as what if there are no loans associated with the account.

Let's start by (simulation of) getting a user object from a database.

function getUser(id) {
  console.log(`Reading a user from a database!`);
  let user;
  setTimeout(() => {
    console.log(`Recieved user data!`);
    user = { "ID": id, "Account number": "1234567890" };
  }, 2000);
  return user;
}

console.log("listening for events");
getUser(1);
console.log("still listening for events!");

Let's try to store and display the user returned from the database:

function getUser(id) {
  console.log(`Reading a user from a database!`);
  let user;
  setTimeout(() => {
    console.log(`Recieved user data!`);
    user = { "ID": id, "Account number": "1234567890" };
  }, 2000);
  return user;
}

console.log("listening for events");
const user = getUser(1);
console.log(`user: ${user}`);
console.log("still listening for events!");

The user object is undefined because the return statement in getUser is executed (at least) two seconds after it was called. So what is returned from getUser will not be available at the time of calling it.

So how can we access the user returned from the getUser function? There are three patterns to deal with asynchronous code:

  • Callbacks
  • Promises
  • Async/await

We will look at each in the following sections.