Workflow sequence for client-side actions

Is anybody able to confirm whether these two assumptions about the sequence of client-side workflow steps are true? My own testing has established that they are usually true, but that doesn’t mean it’s guaranteed under load. Thanks in advance!

  1. Will two consecutive workflow steps that each set a custom state to a local value (an option set value, another custom state or a url parameter) always run in sequence, with the first step completing before the second starts? This post confirms that the two steps will be triggered in order, but it also says broadly that the second may start before the first has completed. This makes sense for asynchronous, server-side actions, but it would be surprisingly for purely client-side actions.

  2. If I set an empty custom state of type List of Thing to the result of a Do a Search, and have a separate workflow that listens for when that custom state is not empty, will this latter workflow will only fire when the custom state is fully populated?

I like that bubble doesn’t enforce unnecessary synchronicity just to keep things simple at the expense of performance, but I agree with others here that bubble could do better at making the mechanics more transparent to their users.

  1. Not necessary. If you want to synchronous call the workflow, use the “Custom event.”
  2. What is the means of fully populated? The workflow will call whenever the “custom state is not empty”. Adding data to the custom list using “add list” will still fire only once and does not depend on how many items you add.

Ankur@ Nocodetalks
Looking for a Bubble Coach? Check out here

Thanks for your response. I was hoping I wouldn’t have to wrap every single step in its own Custom Event for client-side workflows, but it seems I need to.

Re “fully populated,” I meant that all the items in the list returned by the initial Do a Search action have been loaded into the custom state. For example, if the search returns ten rows of data, I want to make sure that Bubble has loaded all ten rows into the custom state before the second workflow that’s listening is called. In theory, the custom state meets the condition of not empty as soon as the first item is added, but I want to make sure the workflow won’t fire until bubble has loaded all of them.

I don’t think using the “add list” action will treat the operation as complete as soon as the “first item is added.” It will wait till all the data has been added.

Do test it. Try to add the 500 items using the “add list” and see if that case.

In response to (1): The Element > Set State action is entirely synchronous, AFAICT. Which is to say, if you have multiple Set State actions in series, even if they don’t depend upon each other (via "Result of [some previous step]), they complete one-after-the-other.

You can easily test this by making a workflow that does Set State for the results of a long-running search (stored somewhere as a list called “Results of long running search”), followed by Set State for some simple scalar value (e.g., set a number state to 5), followed by a Set State that sets a number custom state to “somewhere’s Results of long running search: count” (let’s call that state “Length of long running search”).

Set up a text element to that reports: (1) Results of long running search:count, (2) the number custom state set in step 2, and also (3) “Length of long running search” (the last numeric state you set in step 3).

What you’ll observe is that even though there’s no reason for Bubble to wait to populate (2), it will stay at 0 (representing the fact that it’s empty) until (1) finishes.

You can run all sorts of variations on that experiment and you’ll see that Set State actions entirely synchronous. (They are not just started in sequence – a Set State that follows another Set State will not trigger until the previous one is completed, AFICT.) This is true specifically of the Element > Set State action. Other actions can have different behavior.

In response to (2): I’m being a little didactic here, but of course a custom state that is a list is never empty (list type states do not have the property of being empty, but we can check their length via the :count property or check if their first item is not empty to establish if the list has any items.

Anyway, if you do Set State and the state you’re setting is of list type and you set it to the results of a Search, yes, the custom state’s state's:count or state's:first_item will only go high after all the item values have been fetched and the state has essentially “published” its values back to Bubble. (Of course if the Search results in a list of zero length, neither will ever go high.)

2 Likes

Thanks @keith for your detailed answer as always. Also, you just blew my mind. I didn’t even think to confirm that Set State actions that need to perform asynchronous activity to get their value (eg a db read) actually run asynchronously in bubble workflows. I just assumed they did. I did some testing as you suggested and you’re right that they are indeed synchronous all the way. In fact, there doesn’t seem to be a way at all to load more than one db dataset into its respective custom state in parallel, which will lead to some missed page load time optimization opportunities.

On (2), I only discovered the “list is empty” rabbit hole after I wrote my original post. (More comprehensive posts by you on that topic, so thanks again.) I came to the conclusion that there is no way to tell the different between these two states: “This custom state of type List of X has not been assigned a value using a Set State action” and “This custom state of type List of X has been set to a list that has zero items.”

1 Like

To be clear: they (Set State operations) run synchronously. (not asynchronously). That is, the preceding step fully completes before the next step. So you can always safely reference the value in a Custom State without worrying that the workflow jumped ahead before the updated value of the state is actually available.

For more on how Search works (and that Search operations are SERIAL and not PARALLEL), see my other posts from today:

And my reply to my own post:

On “lists are never empty”:

The reason that a list is never empty is that Bubble (and the Bubble expression builder) is just a clever abstraction layer on top of JavaScript. And, in JavaScript there “sort of” aren’t arrays. Arrays exist, but they are just a special case of objects. All objects are unique of course, even if they hold the same values. The data type of an Array (in JavaScript) is object. Arrays have a prototype (by which we can identify them as being Arrays), but at the end of the day they are just objects.

So, if you open your console and do:

var a = [1, 2, 3, 4] // a is an array of four items 1 thru 4

And then:

var b = [1, 2, 3, 4]

And then you ask, is “a” the equivalent to “b”?

a == b // “==” is the equivalency operator in JS, not to be confused with “=” (the assignment operator).

The result will be false. Because “a” is one object and “b” is another object. So, while they hold identical values, these can never be the same thing (object).

The only way that a can be identical to b is via an object reference:

b = a // we assign b to be the same thing as a

Now b and a refer to the same object (a single instance of the array/object [1, 2, 3, 4] ).

So, what is “empty”? Empty is Bubble’s equivalent of the special JavaScript value null. As it so happens, null is also an object (it is not its own data type). It is a unique object that never exists naturally, it can only be called into existence by the programmer and it is a way of saying, while some thing is not undefined, neither does it have any value or any data type beyond object.

Additionally (and this is a feature of all drag & drop programming environments), in Bubble there is no concept of undefined. For us to reference something in a drag and drop environment, it must have some label, some symbol. And further (if there’s an Issue Checker as in Bubble or we care about data types as does Bubble), there needs to be additionally metadata (e.g., where it’s located, what types of data it can store) about a symbol for it to be useful…

So there’s no such thing as “a thing that is labeled but does not yet exist” in such environments. So “empty” kind of plays a dual role: It represents both null and in some special circumstances, the state of being undefined. Nothing is ever undefined, so we either simply cannot reference it or it is assumed to be null/empty.

(Example: Try to use the expression builder to reference some custom state that does not exist. You can’t do it. Instead, you would have to define the custom state first (at which point it is empty/null, but it is most definitely not undefined). And, in fact, in such situations, Bubble actually invites you to define a new custom state! That’s the way graphical/drag & drop environments work.)

Back to null:

While null is an object, we cannot modify it. We cannot add any additional properties to it, nor does it have any properties. Null is just… null. And, in the same way that all instances of the integer 5 are the same, all nulls are the same. 5 == 5, null == null, 5 !== anything else, similarly null !== anything else.

And so, because Bubble lists are Arrays, they are also objects. And objects are unique. And some Array object can never be the special object null. (Additionally, null is not an Array, so no Array can ever be null and no null can ever be an Array.)

And, thus, an Array (a List in Bubble) is never null (empty in Bubble terms).

The same is true in pure JavaScript. An Array can have no elements: Here is an Array of zero length:

var a = [] // a is an Array, but it has no elements, its length is 0 (zero).

… but a is still an object and thus is unique (and it is definitely not null!).

All indexes of a are undefined. a[0] (the first element of the array) does not exist and thus is undefined. As is a[1] and on and on and on.

Now, in the same way that our Array a can have 0 length, Bubble Lists can also have zero length. But they are still objects and thus are not equivalent to empty/null.

So we can’t ask if a List is empty (null), but we can ask if the first item is “empty” (this is one of those cases, where Bubble equates null and undefined – technically speaking, the first item in a List with no items is undefined, but since Bubble can’t have that, it evaluates to empty).

The other thing we can do of course is ask how many elements a List (Array) has. In Bubble terms this is the List’s :count (in JavaScript the same thing is an Array’s .length property).

A Bubble List without any items in it will have a :count of 0. A JavaScript array without any items in it will have a .length of 0.

NOW, to the question of, “which is better… to test if a Bubble List has a :count < 1 or if the :first item is empty”?

In vanilla Bubble, it doesn’t really matter. HOWEVER, in plugin land, where we encounter some Lists (in fact, most of the Lists we encounter) that are themselves objects, it turns out that we can know the :count of the List (the .length of the Array of items that List represent) before accessing any of the items in the List.

This is because the results of a Search is a List object – which is a simple JS object with two methods on it: a .get() method (to retrieve individual items in the List) and a .length() method (which instantly returns to us the number of items in the List – it doesn’t query the database to know this, the value just travels along with the List).

So, in a plugin, if I want to evaluate whether a List presented to me has any items or not, I’d just look at List.length() and, if it’s 0, I know that there are no items in the List and do not have to bother to .get() the first item and examine it. (Nor should I bother to fetch the items in the List at all, I already know that the JavaScript equivalent of such a list is []).

Anyway, that’s a little story about Lists. :wink:

Postscript: The only place I know of where a Bubble programmer can access this same functionality is in my List Shifter plugin. There’s an exposed state in List Shifter named “Item Count”. When List Shifter is first initializing, I inspect the .length() property of the incoming List and immediately publish it to that output just before fetching the items in the List.

In this way, you might (for example) send List Shifter some giant List, but you can know the size of it well before List Shifter finishes initializing, just by keeping an eye on that exposed state.

2 Likes

P.P.S: Well, you could do Some Search:count and this will basically instantly return the number of search results, without fetching any of the items. But Bubble seems to insist that this number may not be accurate for any and all searches, as described here.

1 Like