Keeping things pretty high level, we oftentimes expect code to run in the exact order we write it, ie:
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.
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:
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.
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.
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.
Let's take a look at the logs and output of this JS query:
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:
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!
Everything that runs async runs as a Promise, particularly:
- Model updates (like setValue)
- 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.
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
noAsyncJS query's 'ran successfully' notifications were the first things logged in our console. In those cases anything triggered via the
onSuccessevent 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
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 !
Async.json (29.5 KB)