When the clouds combine...

How fast is Netlify + Astra?

A JavaScript newbie's journey to understanding async and await by timing how long it takes THIS webapp to make asynchronous API calls.

After my first introduction to JavaScript about 6 months ago, I wanted to reach a deeper understanding of the builtin asynchronous programming concepts within the JavaScript language: async, await, and promises. To teach myself these concepts, I came up with a close to real-world, testable question for this live app:

How long will it take to fetch data via REST APIs from a webpage?

There are many, many ways we can do this. This post (which is a real, live app) focuses on timing the roundtrip API calls between two different cloud based services:

1. Webapp hosting and content delivery network (CDN): Netlify
2. Cloud Database: Astra

The rest of this post details how I setup the timing tests using asynchronous API calls, and a few hiccups I ran into along the way. Go here for a 10 minute tutorial on how to setup Netlify functions to make API calls to an Astra database.

How do we setup asynchronous timing tests?

Let's setup 10 tests. Each test will make 10 asychronous (in parallel or simultaneous) API calls to our database in Astra. Our goal is to time the round trip for each set of asynchronous API calls from a simple webapp (literally this one you are reading right now) to our cloud database and back. The pseudocode for this would look like:

      
  1  Write a function to run multiple rounds of tests
  2  For each round of testing
  3     Start a timer in the app
  4     Call a function that will perform asynchronous API calls
  5     Stop the timer in the app
  6     Display the total time for that round
    

The above pseudocode translates into the following JavaScript:

      
  1  function runTests(rounds, numberAPICalls) {
  2    for (round = 0; round < rounds; round++) {
  3      var startTime = new Date();
  4      timeTest(numberAPICalls).then(() => {
  5        var finishTime = new Date();
  6        var timeTaken = finishTime - startTime;
  7      );
  8    }
  9  }
    

The work of doing the asynchronous API calls is in the timeTest function; we will get to what that does after we take a look at my first mistake.

Click the button below to run this webapp's timing suite with the function definition above. Before reading the answer written below the output, can you see what is wrong with the output?

What's wrong with these results?

There are two glaring mistakes with the output above that led me to think we weren't setting this up correctly. First, the Round number is the same for each series of tests. Second, the Start Time is the exact same for each test.

Something isn't right here. It seems like the round number and the start time are not updating with each subsequent series of tests. Instead, we want both the round number and start time to wait for the tests to complete before changing value.

This is where we run into the async and await pattern in JavaScript.

What is the async / await pattern and how do we use it here?

Think of async and await as making the code read synchronously (like python), even though it really isn't. This great blog by Mozilla details the definitions and usages of the async / await pattern. The definition of await is:

await can be put in front of any async promise-based function to pause your code on that line until the promise fulfills, then return the resulting value.

We need to pause our code after the test so that the timer can correctly calculate the runtime for each set of asynchronous API calls. Let's change the function above into an async function and tell the code to pause for the entire set of API calls to finish with awaitbefore stopping the timer. This changes the function to the following:

      
  1  async function runTests(rounds, numberTests) {
  2    for (round = 0; round < rounds; round++) {
  3      var startTime = new Date();
  4      await timeTest(numberAPICalls).then(() => {
  5        var finishTime = new Date();
  6        var timeTaken = finishTime - startTime;
  7      );
  8    }
  9  }
    

Click the button below to see the results when using the above structure for your testing skeleton:

Great! This testing suite is now properly timing only one round of API calls at a time. Last up, let's take a look at how we are making the API calls.

How do you kick off a bunch of asynchronous API calls?

I love how easy it is to use Netlify's functions feature. For this project, I created one REST API to make calls from Netlify to the database in Astra. In the timing suite, the API call is made in one async function which waits for the payload to return. You can see the same async / await pattern in the function below to make the API call and wait for the result:

      
  1  const addData = async (testNumber) => {
  2    const response = await fetch("/.netlify/functions/writeRestAPI", {
  3      method: "POST",
  4      body: testNumber,
  5    });
  6    const responseBody = await response.json();
  7  };
    

The last step in our setup is to make simultaneous calls to this API. We need one last async function for this:

      
  1   async function timeTest(numTests) {
  2     const dataPromises = [];
  3     for (let i = 0, ilen = numTests; i < ilen; i++) {
  4       dataPromises.push(addData(i));
  5     }
  6     return await Promise.all(dataPromises);
  7   }
    

I highlighted two sections in the above code snipit. First, let's look at Promise.all(). This is the final piece of key functionality for our testing. The use of Promise.all() here will wait for all asynchronous API calls to return before returning the results.

You can find all of the code for this project here.

Thanks for reading!

Bad Calls Example Good Calls Example