Execution model
Last updated
Last updated
Every action, regardless of its type, goes through the same execution process:
Constraint check: Check whether another execution of the same action is already running, or whether a potential minimum delay has not passed yet. If yes, the execution is added to the queue for later execution. Please refer to the documentation below to learn more.
JS evaluation: Once the constraint check has been passed, JavaScript code inside the action configuration is evaluated. For example, this might be embedded snippets in a SQL query, or evaluating the JSON-body of a REST request. In case the evaluation encounters an error anywhere, the action execution is aborted and marked as unsuccessful.
Action run: The action is now executed as configured, using the JS evaluation results from the second step. The execution highly depends on the action type and, except for JavaScript actions, requires a round-trip to backend. In case the backend responds with an error, or in case the action times out, the execution is aborted and marked as unsuccessful.
JS transformation: Actions might have JS transformation scripts configured. In this case, this code is executed, providing the raw result as input. In case this evaluation leads to an error, the raw action result is still kept as successful execution "before transformation", but the action result "after transformation" is marked as unsuccessful.
Dependency reevaluation: Following the reactive principles of the engine, there might be usages of the executed action that depend on specific properties, like for example data
, dataBeforeTransform
, success
, or executionTime
. After successful or unsuccessful execution of the action, these usages are reevaluated - which can trigger cascaded dependency reevaluation.
The following sections describe some of these steps in more detail.
The same action will never run more than once in parallel, regardless of how it has been triggered. In case an action is triggered while another execution of the same action is still running, this newly triggered execution will wait in the queue until other executions of that action have been finished.
The following example illustrates this mechanism very well:
This example app performs the following operations:
The click-event handler of the "Run" button increments the state variable that fuels the number on the left. You see that it increases immediately every time the button is clicked.
Then, the longRunningAction
is triggered, which waits for 2 seconds and then increments a state variable that is shown on the right.
As you can see, the right-hand number is incremented with exactly 2 seconds delay, because the four executions, which are triggered by quickly clicking the button at the beginning, are strictly executed in sequence.
In this example, it is important that the action that handles the button click does not await
the result of longRunningAction
. If we used await
here, the left-hand figure would also increment with 2 seconds delay, because the handleButtonClick
action will always wait for the longRunningAction
to finish. Executions of handleButtonClick
would then queue up in a similar way like the executions of longRunningAction
do in the example.
With regards to timeouts, actions are currently not restricted in terms of how long they execute. The application will always wait until an action finishes, no matter how long this will take. Due to the sequencing of executions, that means that very long-running actions will block any further executions. It is therefore recommended to keep actions small and atomic, and of course to prevent infinite loops.
The only exception of not having a timeout is the execution of a Salesforce bulk job. This action type is timed out after 120 seconds. We are working on a more consistent timeout behavior, and making it configurable for individual actions.
In many use cases, data that is pulled in from external datasources must be transformed in some way. To accomplish this, any action can be configured to execute JavaScript code to transform the original result of the action. For example, a MySQL
action returns a set of records, and a JavaScript snippets adds aggregations and data cleaning on top.
JavaScript actions do not allow transformation, because the transformation logic can simply be added to the action itself.
A transformation script can be added to an action by navigating to the "Transformation" tab of the action configuration, and activating the toggle there:
When this toggle is activated, a code input appears to write the transformation code. The original result of the action can be accessed via the data
variable. The value that is returned by this transformation code will be placed in the data
property of the action.
You will still be able to access the original result of the action via the dataBeforeTransform
property. In summary:
Use the data
property of an action to access the result after transformation.
Use the dataBeforeTransform
property to access the original result before transformation.
In case no transformation is configured, data
and dataBeforeTransform
will reference to the same result.
In case there will be errors in either the original action, or the data transformation, the action properties will be set as follows:
success
will only be true
if both the original action and the JavaScript transformation ran without errors, and false
otherwise
error
will contain the message of the error that occurred either in the original action or the JavaScript transformation. In case both ran successfully, it will be null
dataBeforeTransform
will contain the result of the original action, even if there was an error in the JavaScript transformation (and hence, success
being false
). In case there is an error in that original action run, it will be null
data
will contain the result after applying the JavaScript transformation. In case there was an error in either the original action, or the transformation, it will be null
Actions can receive arguments for an execution, similar to how regular functions can receive arguments. You can pass arguments of any type to an action run by providing an object as first argument to the trigger
function with args
as key. Formally, the signature of the trigger
function is as follows:
You can access these arguments by using the args
variable inside of an action:
args will always be undefined when an action is previewed. In case you want preview to work properly for an action, you need to use sensible defaults in your code for that case. This certainly is unnecessary and undesired code, and we are working on a feature to deliberately provide explicit arguments in case an action is previewed that is depending on args.
One possible trigger of an action is a UI event, like the click of a button or the change of a toggle. To react to such events, an action must be associated with an event-property of a component in the properties panel. Some of these events will pass specific args
to such action runs by default:
Custom actions of input validation rules will receive the input value of the validated component
An action associated with a table row action will receive the data array of the clicked row
Actions associated with the "On rows update" and "On row add" events of a table will receive an object summarizing the changes
We will continue to add more default arguments like that to specific events, to make it easier for engineers to consider the context of the particular event.
JavaScript snippets in dynamic component properties can not only use properties of other components, but also properties of actions. Due to the reactive approach of our engine, such snippets should also be reevaluated as soon as a utilized property of an action changes. An action run will therefore trigger such updates as soon as it has finished, regardless of whether it was successful or not. You can therefore rely on your snippets always being updated with the latest action property values: