Asynchronous code is code that doesn't necessarily run line-by-line in the order you've written it. In Velo, you will often encounter asynchronous code when you call a function that cannot finish executing immediately and therefore returns a Promise. Also, when you call a backend function that you've written from your page code, you must call it asynchronously.
For example, if you run a query on one of your database collections using the
find()
function, it takes some time for the query to be executed and for the results to be returned. So you have to wait until the operation is Consider the following code snippet where the
find()
function returns a Promise:console.log("1")
wixData.query("myCollection")
.find()
.then( (results) => {
console.log("2");
} );
console.log("3");
The following will be logged to the console.
1
3
2
Notice that the 3 is logged before the
2
even though the console.log()
calls do not appear in that order in the code. That is because the logging of 2
only occurs once the query is finished, but the rest of the code continues to run in the meantime.In this article, we describe how to work with the functions in the Velo API that run asynchronously. We discuss different ways to call such functions and wait for them to finish executing. We do not present a general discussion of asynchronous programming in JavaScript. To learn more about using Promises in a general context, see Using Promises.
A Promise represents a value that is not necessarily known when the Promise is created because it depends on asynchronous communication. A
function that returns a promise is returning a contract to eventually produce a return value at some point in the future. A Promise is said to be fulfilled or resolved when its value is finally known.
There are two main ways to work with Promises that are returned from a function.
then()
- Using this approach your code is generally a little more difficult to read, but you have more control over when your code runs. For example, if your code does not need to wait for a Promise to resolve, you can have other code run in the meantime, thereby improving overall performance.async/await
- Allows you to work with asynchronous code as if it was synchronous, making you code easier to read. However, you lose the option of running other code while waiting for a Promise to resolve.Generally, using
async/await
is preferred when it does not affect the performance of your site because it makes for cleaner code. However, there are some cases where using then() improves performance and is worth the added complexity.then( )
You can use the
then()
of a Promise to specify a function that will be run when the Promise resolves. The specified function receives the value that the Promise resolves to. So, any code that relies on the Promise's resolved value should be placed in the then()
.For example, let's say you want to retrieve some data from a collection and display it in a table. Since the data retrieval is an asynchronous operation, you have to wait until it is completed before using the query results to set the table's data. The
find()
function performs the query and returns a Promise. So the then()
defines a function that runs when the query is finished. That function receives the query results and uses them to set the table's row data. Any code that follows the then()
will run after the query has started, but before the query has finished. Therefore, it cannot use the query results.import wixData from 'wix-data';
$w.onReady( function () {
wixData.query("myCollection")
.find()
.then( (results) => {
// this will only run once the query has returned results
$w("#myTable").rows = results.items;
} );
// code here will run before the query has returned results
} );
Multiple .then( ) Calls
Sometimes, you will need to call another asynchronous function inside the
then()
of a Promise returned by another asynchronous function.For example, let's say you want to use the
fetch()
function to retrieve data from a 3rd party service. That operation is asynchronous because it has to wait for the 3rd party to return a response. Then you want to read the response as JSON. The json()
function of the response object is also asynchronous.You might think that you need to nest one
then()
inside another, which would look something like this:fetch( url, {method: 'get'} )
.then( (response) => {
return response.json()
.then( (json) => {
// do something with the json response
} );
} );
But because
then()
itself returns a Promise, you can call another then()
on that returned Promise. Bottom line, that means you can chain successive then()
calls to perform one asynchronous operation after another.That means the code above should be written like this:
fetch( url, {method: 'get'} )
.then( response => response.json() )
.then( (json) => {
// do something with the json response
} );
Here, the
then()
on line 2 is the then()
of the Promise returned by the fetch()
call. And the then()
on line 3 is the then()
of the Promise returned by the json()
call. The first then()
will only run after the fetch()
call is finished and the second then()
will only run after the json()
call is finished.Returning in a .then( )
Another common misconception is what happens when you use a return inside a
then()
. Some people mistakenly expect that the value the Promise resolves to is used as the return value of the function that the then()
is in.For example, let's say you have a page about cars. On that page you have a
dropdown that users use to select a car model. When a selection is made, you perform a query for a specific car model and populate a table with all the matching items.
You might write some code like this thinking that the
queryCars()
function will return the car items as an array when the find()
Promise has resolved.import wixData from 'wix-data';
export function modelDropdown_change() {
let model = $w("#modelDropdown").value;
$w("#resultsTable").rows = queryCars(model);
}
// This will not work as expected
function queryCars(model) {
wixData.query("cars")
.eq("model", model)
.find()
.then( (results) => {
return results.items;
} );
}
However, the above code will not work as expected. Let's understand why it doesn't work and what we need to do to fix it.
When we return
results.items
, we're not returning it as the return value of the queryCars()
function. Rather, it is returned as the return value of the arrow function passed to the then()
. That means nothing is being returned from the queryCars()
function. So when we call queryCars()
to set the table's rows, we're receiving nothing in return.To remedy this situation, we need to return the result of the Promise's
then()
(or then()
chain). To do so, we simply add a return
before we start the chain that created the Promise. This will return the final link in our chain.So here our chain looks like this:
.query()
- Create a query - returns a WixDataQuery object.eq()
- Modify the query - returns a WixDataQuery object.find()
- Execute the query - returns a Promise.then()
- Define what to do on resolution - returns a PromiseAdding a
return
to the start of our chain means we'll return the Promise returned from the final then()
. Since the value returned from the then()
is itself a Promise, we need to keep this in mind when we call the queryCars()
function.That means our fixed up code should look like this:
import wixData from 'wix-data';
export function modelDropdown_change() {
let model = $w("#modelDropdown").value;
queryCars(model)
.then( (items) => {
$w("#resultsTable").rows = items;
} );
}
// The added `return` on line 13 fixes the problem
function queryCars(model) {
return wixData.query("cars")
.eq("model", model)
.find()
.then( (results) => {
return results.items;
} );
}
Here the
queryCars()
function returns a Promise that resolves to the items from the query results. So when we call the queryCars()
function we have to treat it like a Promise and use a then()
to use its resolved value to set the table's row data.Debugging on the Backend
Calls to
console.log()
that are made from inside the then()
Promise in backend code are not logged in the developer console unless the Promise itself is returned to the client-side code.For example, consider the following backend code:
export function usePromise() {
return fetch('https://site.com/api?q=query', {method: 'get'})
.then( (response) => {
console.log(response.status);
} );
}
As written, if you call
usePromise
from the client-side, the message on line 3 will be logged in the Developer Console. However, if you remove the return on line 2 and call usePromise
from the backend, the message on line 3 will not be logged in the Developer Console.Note
You can use Site Monitoring to view the output of console messages in backend code.
Error Handling
An asynchronous process might fail for any number of reasons. For example if you call a function like
fetch()
that communicates with another server, the call might fail because of a communication error. When a Promise does not resolve successfully, it is said to reject. You can handle rejections by adding a catch()
to the end of your then()
chain. The catch()
receives the value that the Promise rejects with, usually an error.Let's return to the example of retrieving some data from a collection and
displaying it in a table. You can handle errors by adding a
catch()
like this:import wixData from 'wix-data';
$w.onReady( function () {
wixData.query("myCollection")
.find()
.then(results => $w("#myTable").rows = results.items)
.catch(error => {
// handle your error here
} );
} );
Using a
then()
with a Promise allows you to define what code will run when the Promise resolves, while at the same time allowing execution to continue with whatever code comes after the then()
. Another option for async/await
.You use
await
when calling a function that returns a Promise. It pauses the execution of your code until the Promise is resolved. In order to use await
, you need to declare that the function in which it resides is an async
function.Once again, let's return to the example of retrieving some data from a
collection and displaying it in a table. But instead of using a
then()
, we will use async/await
to deal with the Promise returned by the find()
function.Remember, when using a
then()
, we wrote code that looked like this:import wixData from 'wix-data';
$w.onReady( function () {
wixData.query("myCollection")
.find()
.then( (results) => {
// this will only run once the query has returned results
$w("#myTable").rows = results.items;
} );
// code here will run before the query has returned results
} );
Using
async/await
, we can instead write code like this:import wixData from 'wix-data';
$w.onReady( async function () {
const results = await wixData.query("myCollection").find();
// the next line will run only after the query has finished
$w("#table1").rows = results.items;
} );
Here, you can see on line 3 that the callback function passed to the
onReady()
function is declared as an async
function. And when the find()
function is called, it is called using await
. So when the query runs, the execution waits until the Promise of the find()
function resolves. Once the query is done, the query results are stored in the results variable which can then be used to set the table's row data.Note, that when you create an async function, that function returns a Promise. So when you call that function you need to use one of the approaches discussed in this article to deal with the Promise it returns.
For example, here is an async function that queries a collection to find an item with a specified name.
import wixData from 'wix-data';
//...
async function findName(name) {
const results = await wixData.query("myCollection")
.eq("name", name)
.find();
return results.items[0];
}
You can call this function using a
then()
:$w.onReady( function () {
findName("Bob")
.then(item => console.log(item));
} );
Or, you can call it using
async/await
:$w.onReady( async function () {
const item = await findName("Bob");
console.log(item);
} );
Error Handling
When using
async/await
you use a regular try/catch
block to handle any Promise rejections.import wixData from 'wix-data';
$w.onReady( async function () {
try {
const results = await wixData.query("myCollection").find();
$w("#table1").rows = results.items;
}
catch(error) {
// handle the error here
}
} );
Image Credit: https://pixahive.com/photo/promise-illustration/
Previously published at https://support.wix.com/en/article/velo-working-with-promises