Promise Patterns in the Plugin API Version 4

Introduction

This topic is more for more own records, and covers various design patterns of using explicit Promise chaining of the new API’s asynchronous calls. In each example I have elided using a catch statement because I want to implicitly log errors in the Bubble server logs, which the catch statement will block.

Parallel List Loading

In this example we will load two lists asynchronously in parallel, leveraging Promise.all() to load the lists from properties.listone and properties.listtwo:

// Hold the lists
const lists = [Promise.resolve([]), Promise.resolve([])];

// Asynchronously load an array of the first list
if (properties.listone) {
    lists[0] = lists[0]
    .then((_) => { return properties.listone.length(); })
    .then((n) => { return properties.listone.get(0, n); });
}

// Asynchronously load an array of the second list
if (properties.listtwo) {
    lists[1] = lists[1]
    .then((_) => { return properties.listtwo.length(); })
    .then((n) => { return properties.listtwo.get(0, n); });
}

// Access when both are done
const keepgoing = Promise.all(lists)
.then(([xs, ys]) => { /* Do something with the two lists */ });

JSON POST

In this example we will POST to a secure JSON endpoint, assuming the plugin has the bearer token environment variable under context.token and the third party endpoint under context.url:

// Specify the fetch options
const options = {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
        "Accept": "application/json",
        "Authorization": "Bearer " + context.token
    }
    body: JSON.stringify(mydata)
};

// Parse the JSON from the POST
const response = fetch(context.url, options)
.then((r) => { return r.json(); })
.then((j) => { /* Do something with the parsed data */ });

Binary GET

In this example we will GET the binary of a file specified by the plugin parameter properties.url:

const options = {
    method: "GET",
    headers: { "Accept": "application/octet-stream" }
};

// Retrieve the file
const response = fetch(properties.url, options)
.then((r) => { return r.arrayBuffer(); })
.then((a) => { /* Do something with the raw binary */ });

Summation

Those were the three most common use cases I have encountered while updating my plugins. I hope someone finds these useful. I favour using explicit promising chaining because it forces me to reason through and define the exact dependencies between asynchronous calls, rather than spraying and praying awaits everywhere.

15 Likes

Thank you and kudos @aaronsheldon :+1:t2::+1:t2::+1:t2:

2 Likes

I really value when community members share important concepts like @aaronsheldon shared above. I want to add some additional information that could be of use to plugin builders converting to v4 or new plugin builders who are inevitably stumbling upon older posts that are soon to be of no relevance.

Reference information regarding this post can be found here: Updating to Plugin API v4 - Bubble Docs

First, for the basic use case, if you have code that looks something like this:

let auth = context.async(async callback => {
        try {
                const data = await service.authenticate(); 
                callback(null, data);
            }
            catch (error) {
                callback(error);
            }
         });

This can now be simplified in v4 to this (yes I’m aware we are praying and spraying here, but for completeness sake):

let auth = await service.authenticate();

Obviously, the error handling is not being considered but you get the idea hopefully.

This next little nugget I am personally very happy about. Bubble officially legitimized the get_object_from_id and get_objects_from_ids!

These previously undocumented functions are highly useful in some very specific scenarios. However, because they were undocumented there was confusion about their usefulness and availability in the future (see this thread for historical context/information on that dialogue.)

Not surprisingly, the new “official functions” have changed slightly from their previous implementation. These SSA functions can now be found at context.getThingById and context.getThingsByIds.

I’m not going to go into great detail on the specifics and usefulness of these functions…when you find a need for them you’ll understand why they are useful. However, here is some code to explore when you need it:

// properties.id_list is a text list of unique ids passed into the plugin (e.g. ["1652389984169x141277219898878320","1652389984169x141277219898878320"])
var objs = await context.getThingsById(properties.id_list.split(",")); 

for(var x=0; x < objs.length; x++)
{
         var keys = Object.keys(objs[x]._pointer._source);
            for(var y=0; y < keys.length; y++)
            {
               var columnNames =[];
               var values = [];
               var key = keys[y];
               if(!key.startsWith('_type')) {
                    columnNames.push("[" + key + "]");
                    values.push("'" + objs[x]._pointer._source[key]  + "'");
                }
            }
        }

For whatever reason, the Bubble team (I’m guessing intentionally, but can’t be certain) didn’t fully document these two methods so this very likely isn’t the exact way they intended on it being used, but it works beautifully and is lightning fast! Kudos to the Bubble Team for keeping these functions and making them official.

2 Likes