This post has lots of code examples showing promises, async/await and unit testing async functions. There is a coding challenge at the end to test your learning.

What do promises solve in our code?

Whenever you wanted to resolve some data asynchronously it had to be done via a callback. In complex applications this led to “callback hell”, when the first data fetch was resolved another data fetch needs to happen afterwards. This would repeat a few times, creating a “nice” sideways pyramid in your code.

Promises mostly solved that problem by chaining. Each step resolved data and passed it along to the next then function.

Street art on a wall, Face with sun glasses Photo by Alex Holyoake on Unsplash

Using promises

Example of the promise api:

new Promise(function(resolve, reject) {
  // if OK
  resolve(data);
  // if ERROR
  reject(error);
}).then(function(data) {
  return turtle;
}).then(function(turtle) {
  return turtleToRat
}).catch(function(error) {
  // handle an error
});

The fetch function is available in most browsers and returns a promise. In the example below there are two functions, one fetches a Github user and the other gets the user repositories. This builds a Github profile object containing the user profile information and an array of repositories.

Example of promises to fetch Github profile and repositories:

function fetchGitProfile(username){
  return fetch(`https://api.github.com/users/${username}`)
    .then((data) => data.json())
    .then(({bio, company, followers, following, repos_url}) => ({
      bio,
      company,
      followers,
      following,
      repos_url
    }));
}

function includeGitRepos(user){
  return fetch(user.repos_url)
    .then((data) => data.json())
    .then((data) => data.map(({name, stargazers_count}) => ({
      name,
      stargazers_count
    })))
    .then((repoList) => {
      return {
        ...user,
        repoList
      };
    });
}

function log(data) { 
  console.log(data);
}

fetchGitProfile('rkotze')
   .then(includeGitRepos)
   .then(log);

This is probably more understandable than trying to use callbacks only. However, you still need to understand the promise API for this code to be 100% clear. This is probably fine for most developers proficient in JavaScript, as promises have been around for quite some time.

Learn more about Promises on Mozilla and JavaScript info.

Using async/await:

In ES7 the async and await keywords are available. These need to be used together to resolve asynchronous actions. Importantly await must be used within an async function and can not be used on its own. An interesting feature is that async/await are compatible with promises. If a function returns a promise you can use the await to resolve it, or if the async function returns it is possible to use .then.

Basics of async/await:

async function resolveMyData() {
  const data = await fetchData('/a');
  return await fetchMoreData('/b/' + data.id);
}

Example of async/await to fetch a Github profile and repositories:

async function fetchGitProfile(username) {
  let profile = await fetch(`https://api.github.com/users/${username}`);
  let { bio, 
       company, 
       followers, 
       following, 
       repos_url 
      } = await profile.json();
  
  return {
    bio,
    company,
    followers,
    following,
    repos_url
  };
}

async function includeGitRepos(repoUrl){
  const repo = await fetch(repoUrl)
    .then((data) => data.json());
  
  return repo.map(({name, stargazers_count}) => ({
    name,
    stargazers_count
  }));
}

async function resolveGithubProfile() {
  const profile = await fetchGitProfile('rkotze');
  const repoList = await includeGitRepos(profile.repos_url);
  console.log({
     ...profile,
     repoList
   });
};

resolveGithubProfile();

In the example above, you can see in includeGitRepos function that it is possible to mix in promises with the await keyword, await fetch(repoUrl).then((data) => data.json());. It is less obvious if you are not familiar with fetch API, but this is also returning a promise.

However, since it is easy to mix the two approaches it is probably best not to do this within a function for consistency reasons. Instead of using the .then() inside of an async/await function, use await to resolve all promises.

It is evident that async/await is syntax sugar for promises because the return object of one of these functions is a promise. Notably, the code is cleaner making it easy to read and should be easy to migrate from promises.

Resolve multiple async calls in parallel

In the situation where you don’t depend on resolving one fetch to start another, then there is no need to await each fetch. Instead, you can trigger them in parallel and resolve each of them after the request has been made:

async function resolveProfilesInParallel() {
  const rkotzePromise = fetchGitProfile('rkotze');
  const octocatPromise = fetchGitProfile('octocat'); 
  const rkotze = await rkotzePromise;
  const octocat = await octocatPromise; // this will complete in the same time as rkotzePromise.
  return [rkotze, octocat, "done!"];
}

Alternatively using Promise.all achieves a similar effect. The result is an array in the order of the calls:

Promise.all([fetchGitProfile('rkotze'), fetchGitProfile('octocat')]).then(function(values) {
  console.log(values);
});

Unit testing

How would you unit test an async/await function?

Test frameworks like Mocha JS, Jest and Jasmine support async testing. Below are some of the ways this is achieved:

describe('github profile', function() {
  // mockout the fetch
  it('fetch profile rkotze', function(done) {
    fetchGitProfile('rkotze').then(function(data){
      expect(data).to.eventually.have.property("bio");
      done();
    });
  });
});

The example above uses the done callback in the it function to tell the framework the test is completed.

describe('github profile', function() {
  // mockout the fetch
  it('fetch profile rkotze', function() {
    return expect(fetchGitProfile('rkotze')).to.eventually.have.property("bio");
  });
});

The example above supports returning a promise to know when the test is complete. Also this example uses chai plugin chai-as-promised.

describe('github profile', function() {
  // mockout the fetch
  it('fetch profile rkotze', async function() {
    const profile = await fetchGitProfile('rkotze');
    expect(profile).to.have.property("bio");
  });
});

Lastly, the example above shows how to use async/await within the test.

Coding challenge

Codewars kata for async/await

Codepen examples of: