Async, Await and Promises! Oh My!

image

Writing code with asynchronous JavaScript can be tricky, so we wanted to share a little bit about how this works and some examples to help those who may be struggling a bit with this.

First things first, what is asynchronous JavaScript?

Keeping things pretty high level, we oftentimes expect code to run in the exact order we write it, ie: first a => then b => lastly c. JavaScript is generally more than happy to meet our expectations as the following code block demonstrates:

First we define variable a and assign it a value => then we define a variable b and assign it a value => lastly we define a variable c and assign it a value determined by the previous variable values.

While easy to follow along, this can create ‘blocking’ code, where the evaluation of a line of code can take a long time, and prevent other pieces of code from executing. Enter asynchronous JavaScript! Asynchronous code can run in the background, and even if it takes a long time to complete, it doesn’t prevent other code from running in the interim. This is great, but it can break the above mental model on how we expect code to run.

Let's see asynchronicity in action

Here we declare a variable and assign it a value. Then we iterate over that array and trigger a Retool REST API query passing in each element as additional scope. We are utilizing the Postman Echo API's delay endpoint which will wait the specified amount of seconds before responding.
ie: if we call https://postman-echo.com/delay/2, we expect the API to wait 2 seconds and then return:

 {“delay”: “2"}

We are then assigning it's return value to the variable data, and logging the value of data to the console. For reference, this is how the postmanDelay query is set up:

While we might expect things to run in order as before, the results are a bit different:

Our JS query completes first, then the console logs (which log empty objects for our data variable), then the triggered queries finally finish! Since triggering the REST API queries is an async action, that happens in the background and the other code continues to run. This certainly doesn't get us what we want, and doesn't allow us to do anything with the return from each API query.

Utilizing the async and await keywords from JavaScript allow us to control the flow:

As we can see, the logs now flow in an order we might expect. Our JS query still finishes first, but each triggered API query runs in succession with the appropriate console.log statement running afterwards (with the correct data)! This allows us to use that data, but isn't really storing it anywhere for use in the app. We have a lot of information in our docs on this, so highly recommend you check them out here. We'll also explore this more in the next section.

Promises

Promises are pretty complex but there are lots of great resources out there on them. One site that has a ton of good information and also has exercises to play around with is: https://javascript.info/async

Perhaps our most commonly used pattern with promises is returning an array of async query results. It's documented here, but we can see how it might help us in using the REST API data from above in our apps. One thing to note is that we are using JS map method to iterate over the array instead of forEach, since you cannot return results out of a forEach loop, but map is designed for this purpose.

promises

Let's take a look at the logs and output of this JS query:

logs

We can see that the promises array contains Promise objects that contain all the data we need from each successful call to the API.
Using the Promise.all method, we can turn that array of promises into an array of data for use in our app:

state

Working with asynchronous code and promises can feel a bit heavy, but simple iterations on this pattern will get you started in the right direction!

Recap
  • Everything that runs async runs as a Promise, particularly:

    • Model updates (like setValue)
    • utils
    • table.getDisplayedData()
    • Query triggers
  • When a method is async that means that the code beneath it will likely complete before it does. As we saw above, using async / await is super useful here.

  • Most javascript in Retool is already done in an async function so you’re free to use await, but you will have to define your own functions as async, as we saw in the example with the callback.

  • One important thing to note is that the outer (Retool JS query) function returns as soon as all the interior code has been run (but not necessarily completed). As we saw in the screenshots above, the async, noAsync JS query's 'ran successfully' notifications were the first things logged in our console. In those cases anything triggered via the onSuccess event handler would run (and potentially complete) before the REST API returns etc were handled. This can make chaining JS queries with async code difficult! Using Promises can help here, as we see in the execution of the promises JS query.

  • If you are interested in learning more, here is a pretty amazing video that goes into details on how all of this works in the browser. Hope you enjoy it as much as I do!

Hope this is helpful for everyone! It can be an intimidating and confusing topic, but this should lay the groundwork for using async, await and promises in Retool. I'm attaching a simple app with all the above code so you can play around with it. As always, try it out and if you have any issues or questions post here on our Forum. We'd love to see what you all are doing :grinning:!
Async.json (29.5 KB)

15 Likes