November 3, 2021
When a JavaScript Automation Script is executed, it is executed from top to bottom. However unlike Python / Jython scripts, functions are elevated and can be called before they are declared. This provides the opportunity for JavaScript Automation Scripts to be better organized with a clearly defined execution path.
In this post we will look at how functions can be used to organize your scripts into logical blocks of functionality, not only to make them easier to read and maintain but to also avoid incidental or accidental behavior.
It is common to write JavaScript Automation scripts without functions, similar to an old BASIC program: Just a series of statements executing from top to bottom. However this approach quickly breaks down beyond the most simplistic scripts, and as many have discovered, these scripts become unreadable and very difficult to debug and manage.
Fortunately, JavaScript provides functions to organize your script into logical blocks of discrete functionality. Functions are declared with the function
keyword followed by the function name, and may optionally have one or more parameters contained within parentheses. The body of the function follows, and is defined by statements contained within curly brackets.
Function names follow the same rules as variables, with letters, numbers, underscores, and dollar signs being valid. While there are no special meanings for function names within JavaScript, there is a useful convention of prefixing the names of private functions with an underscore. Since JavaScript does not provide a native mechanism for function visiblity or scope, this convention provides information to other developers about which functions are considered internal and should not be called externally.
Function names must be unique and unlike other languages, JavaScript does not support function overloading: Defining functions with the same name but different parameter signatures.
Functions are executed when something invokes or calls it using the function's name, providing any parameter values. All parameters defined by a function are considered optional, and will remain undefined
if not provided.
A function will exit once its body has been evaluated or it reaches a return
statement. The return
statement can also return a value from the function as seen in exampleFunction3
.
// No parameter functionfunction exampleFunction1() {// statements go here.}// Function with two parametersfunction exampleFunction2(parameter1, parameter2) {// statements go here.}// Function with the private function name conventionfunction _exampleFunction2(parameter1, parameter2) {// statements go here.}// Function with a return value.function exampleFunction3(parameter1, parameter2) {return parameter1 + parameter2;}// Invoking a function without defined parametersexampleFunction1();// Invoking a function with parametersexampleFunction2("exampleValue1", "exampleValue2");// Invoking a function without providing parameter values.exampleFunction2();// Invoking a function and obtaining the return value: result will be 3var result = exampleFunction3(1, 2)
Consider the following Automation Script where:
WAPPR
the DESCRIPTION
attribute is required,APPR
the WORKTYPE
attribute is required, CAN
then do nothing,OWNER
attribute is set to the current user name.MboConstants = Java.type("psdi.mbo.MboConstants")var mboStatus = mbo.getString("STATUS");if (mboStatus === "CAN") {// do nothing because in CAN status the record is read only} else if (mboStatus === "WAPPR") {mbo.setFieldFlag("DESCRIPTION", MboConstants.REQUIRED, true);} else if (mboStatus === "APPR") {mbo.setFieldFlag("WORKTYPE", MboConstants.REQUIRED, true);}mbo.setValue("OWNER", mbo.getUserInfo().getUserName());
Of course if the record is in CAN
status then the work order is read only and OWNER
attribute cannot be set. However, because the script evaluates top to bottom in its entirety, the final statement is executed regardless of the status checks.
The options to avoid this are either setting the OWNER
value in each status check or by adding another check at the end of the script:
MboConstants = Java.type("psdi.mbo.MboConstants")var mboStatus = mbo.getString("STATUS");if (mboStatus === "CAN") {//do nothing because in CAN status the record is read only} else if (mboStatus === "WAPPR") {mbo.setFieldFlag("DESCRIPTION", MboConstants.REQUIRED, true);} else if (mboStatus === "APPR") {mbo.setFieldFlag("WORKTYPE", MboConstants.REQUIRED, true);}if (mboStatus !== "CAN") {mbo.setValue("OWNER", mbo.getUserInfo().getUserName());}
A clearer way to achieve this is by wrapping this logic in a function and using the return
statement to explicitly terminate the code execution path and return to the calling statement.
MboConstants = Java.type("psdi.mbo.MboConstants")setRequiredFields();function setRequiredFields() {var mboStatus = mbo.getString("STATUS");if (mboStatus === "CAN") {// do nothing because in CAN status the record is read onlyreturn;} else if (mboStatus === "WAPPR") {mbo.setFieldFlag("DESCRIPTION", MboConstants.REQUIRED, true);} else if (mboStatus === "APPR") {mbo.setFieldFlag("WORKTYPE", MboConstants.REQUIRED, true);}mbo.setValue("OWNER", mbo.getUserInfo().getUserName());}
Not only does this allow for the function to return and stop further evaluation if the status is CAN
, but it also scopes the variable mboStatus
to this function and helps avoid variable name conflicts. This also encapsulates the functionality of setting required fields and makes the code easier to read.
However, as this script becomes more complex, the number of function calls may increase as well. The example below illustrates adding another function. In a real world script the list of function calls can quickly become complicated and may evolve to have conditional logic about which functions will be called based on record state.
MboConstants = Java.type("psdi.mbo.MboConstants")setRequiredFieldsAndOwner();setReadOnlyFields();function setRequiredFieldsAndOwner() {var mboStatus = mbo.getString("STATUS");if (mboStatus === "CAN") {// do nothing because in CAN status the record is read onlyreturn;} else if (mboStatus === "WAPPR") {mbo.setFieldFlag("DESCRIPTION", MboConstants.REQUIRED, true);} else if (mboStatus === "APPR") {mbo.setFieldFlag("WORKTYPE", MboConstants.REQUIRED, true);}mbo.setValue("OWNER", mbo.getUserInfo().getUserName());}function setReadOnlyFields() {var mboStatus = mbo.getString("STATUS");if (mboStatus === "CAN") {//do nothing because in CAN status the record is read onlyreturn;} else if (mboStatus === "WAPPR") {mbo.setFieldFlag("DESCRIPTION", MboConstants.READONLY, false);} else if (mboStatus === "APPR") {mbo.setFieldFlag("WORKTYPE", MboConstants.READONLY, false);}}
In the C
language and its derivatives such as C++
and Java
there is a special function named main
that acts as the entry point for a program. We have found that implementing this convention in Automation Scripts provides a clear entry point to the script and makes it much easier to follow the execution path. Using the previous example, we have restructured it with a main method, allowing us to evaluate for the CAN
status once and clearly halt execution if the condition is met.
MboConstants = Java.type("psdi.mbo.MboConstants");main();function main() {if (mbo.getString("STATUS") === "CAN") {return;} else {setRequiredFieldsAndOwner();setReadOnlyFields();}}function setRequiredFieldsAndOwner() {var mboStatus = mbo.getString("STATUS");if (mboStatus === "WAPPR") {mbo.setFieldFlag("DESCRIPTION", MboConstants.REQUIRED, true);} else if (mboStatus === "APPR") {mbo.setFieldFlag("WORKTYPE", MboConstants.REQUIRED, true);}mbo.setValue("OWNER", mbo.getUserInfo().getUserName());}function setReadOnlyFields() {var mboStatus = mbo.getString("STATUS");if (mboStatus === "WAPPR") {mbo.setFieldFlag("DESCRIPTION", MboConstants.READONLY, false);} else if (mboStatus === "APPR") {mbo.setFieldFlag("WORKTYPE", MboConstants.READONLY, false);}}
By organizing your code into functions you can provide clear execution logic and avoid accidental fall through execution. By using the main
function convention the entry point and execution path for the script is clearly defined and easy to follow, which reduces errors and simplifies troubleshooting.