Callbacks and Promises with startRoll

This describes an optional feature of startRoll. It’s not necessary to know this. Future examples will not use it, but if you want to use it in your own code, you can.

Much of Roll20’s sheet worker code is based on asynchronous functions. getAttrs, setAttrs, getSectionIDs, and also startRoll and finishRoll are callback functions. The following code should be familiar:

getAttrs(['an_attribute'], values => {
  const score = +values.an_attribute || 0;
});Code language: PHP (php)

In this code, values is the callback. It is created by the getAttrs function, and only exists inside the function. In the following code, an undefined value would be sent to the console.

getAttrs(['an_attribute'], values => {
  const score = +values.an_attribute || 0;
});
console.log(values);Code language: JavaScript (javascript)

At the time that console command is run, the getAttrs function has finished, and values is no longer defined. To make that code work, you’d have to do this:

getAttrs(['an_attribute'], values => {
  const score = +values.an_attribute || 0;
  console.log(values);
});Code language: JavaScript (javascript)

The values callback only exists inside the getAttrs function.

Modern JavaScript uses a feature called Promises. A promise is a function that waits for a result, and would look like this:

const values = await getAttrs(['an_attribute'], values);
const score = +values.an_attribute || 0;
console.log(values);Code language: JavaScript (javascript)

Using the await keyword tells roll20 to stop the code here and wait for a result, then proceed. You can create linear code that is easier to read and write.

Notice that you assign the promise function to a variable. Instead of creating a callback variable named values, we create a normal variable called values, and can then use it just like any other variable.

startRoll: Async and Await

At the moment, Promises are only available for the startRoll function. You can’t do this with any other function.

You must also tell roll20 that the following code includes a Promise with the async keyword, like this:

on('clicked:simple', async () => {
  const roll_high = await startRoll('%{template:default} {{name=Roll High}} {{roll=[[{1d10,1d8,1d6}kh1]] }}')
  finishRoll(roll_high.rollId);
});Code language: JavaScript (javascript)

Use async in the last function that contains the startRoll function. So if you used getAttrs, that would look like this:

on('clicked:simple', () => {
  getAttrs(['an+attribute'], async values => {
    const roll_high = await startRoll('%{template:default} {{name=Roll High}} {{roll=[[{1d10,1d8,1d6}kh1]] }}')
    finishRoll(roll_high.rollId);
  });
});Code language: JavaScript (javascript)

Elegance vs Complication

I tend not to use the promise version of startRoll, and I won’t usually be using it in this series. It is better code, but it also creates complications. You have to remember to include the async keyword and might have to place it in a different place depending how your function is built. Also, you have a bunch of functions that should be written this way but cannot, like getAttrs and setAttrs, so you have to remember two different ways of writing your functions.

If roll20 ever upgrades those other functions to use promises, i will switch to this method immediately because the code is more elegant. But right now it creates unneccessary complications.

There are rolls where I do find it worth using, and I will include one of those for sake of example. But if you like them, by all means use them.

Series Navigation<< Computed Properties and CRP Roll TemplatesAction Buttons and the MacroBar >>

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.