ServiceNow Protip: 3 JavaScript methods you NEED to know

Licensed FILE #: 212070274 Preview Crop Find Similar DIMENSIONS 7360 x 4912px FILE TYPE JPEG CATEGORY People LICENSE TYPE Standard focused young man in eyeglasses working with desktop computer

Hey, my fellow ServiceNow Developers!

Are you tired of the for loop cluttering your code with i, j, and other iterator variables? Do you wish there were a cleaner way to handle the data in your Script Includes, Business Rules, and other scripts?

You’ve heard about ServiceNow starting to support ES6, giving you access to some neat functionality. But did you know that some excellent methods from the previous versions of JavaScript work in ServiceNow right now that can help you write cleaner and more maintainable code?

In this ServiceNow Protip, we’ll review three of our favorite lesser-known ES5 JavaScript methods you can use today to elevate your ServiceNow scripting game.

1. [].forEach()

The forEach() method is a simple and helpful JavaScript method that allows you to perform a specific action for each element in an array. forEach() makes it easy to process or manipulate the elements in the array cleanly.

forEach() takes two arguments:

  1. callback: This function executes for each item in the array.

  2. thisArg: An object to use as this inside the callback.

Each time the callback executes, it will receive the following three arguments:

  1. item: The item in the array at the current index.

  2. index: The zero-based index of the item.

  3. array: The entire array.

Before forEach()

For context, let’s take a look at looping over an array of fruits using a for loop.

var fruits = ["apple", "orange", "tomato"];

for (var i = 0; i < fruits.length; i++) {
    gs.info(fruits[i]);
}
The names of the three fruits are logged into the ServiceNow background script.

While we get the result we want because ES5 does not allow using the let keyword, our i variable is declared in the global scope. We are also checking the length property of the array every iteration. While we can optimize for the length lookup by storing length in another variable prior to doing our loop, this can easily become messy.

If we’re not careful we can accidentally affect other loops that may be in the same scope using the same variable names. forEach() mitigates this by taking care of the iteration of a loop, removing the need for additional variable declarations.

Examples

Say you had an array of fruits and wanted to log each fruit in that array.

var fruits = ["apple", "orange", "tomato"];
// This will loop over every fruit and log the fruit name
fruits.forEach(function (fruit) {
    gs.info(fruit);
});
The names of the three fruits are logged into the ServiceNow background script.

As you can see, we don’t have to worry about declaring i or checking the length of the array. We also automatically get fruits[i] passed in as an argument to the callback function. As a result, writing most of your for loops is much simpler.

Let’s see how this works with a more complex example. First, let’s take an array of objects representing users in our system. For now, we’ll have the users’ sys_id, name, and email addresses.

var users = [
    {
        sysId: "62826bf03710200044e0bfc8bcbe5df1",
        name: "Abel Tuter",
        email: "[email protected]"
    },
    {
        sysId: "5137153cc611227c000bbd1bd8cd2005",
        name: "Fred Luddy",
        email: "[email protected]"
    },
    {
        sysId: "f298d2d2c611227b0106c6be7f154bc8",
        name: "Bow Ruggeri",
        email: "[email protected]"
    }
];

If we wanted to log the information for each of these users, we could use forEach() instead of a traditional for loop.

// Loop over every user and log out their information
users.forEach(function (user) {
    gs.info(user.name + " (" + user.email + ") has a sys_id of " + user.sysId);
});
The users' names, email addresses, and sys_ids are logged into the ServiceNow background script.

Caveats

With forEach(), there are a few things to remember. First, there is no equivalent to the break keyword when using forEach(). A for loop may still be the best option if you need to stop the loop.

Secondly, the this keyword inside a forEach() callback differs from the parent function’s by default. By default, the callback’s this keyword is the global object of the environment.

Let’s say you had a user object with a teamMembers property. teamMembers is an array of sys_ids. On this object, there is a method called sendEmail, which will send an email to a single person. We also have a method called sendEmailToTeam, which will loop over all the teammates and call this.sendEmail.

var user = {
    teamMembers: ["62826bf03710200044e0bfc8bcbe5df1", "5137153cc611227c000bbd1bd8cd2005", "f298d2d2c611227b0106c6be7f154bc8"],
    sendEmail: function (userId, message) {
        // code to send an email
        gs.info("Email to " + userId + " sent!");
    },
    sendEmailToTeam: function (message) {
        this.teamMembers.forEach(function (teamMember) {
            // This will not work as expected
            this.sendEmail(teamMember, message);
        });
    }
};
// Doesn't work!
user.sendEmailToTeam("Hey team!");
An empty Script execution in a ServiceNow background script.

Unfortunately, this will fail because the function’s this keyword is not the user object.

We can fix this by using the second thisArg argument for the forEach() method.

var user = {
    teamMembers: ["680bf007dbcb1010b5a9a3a4b3ee4b3c", "e6a3f6e1dbcb1010b5a9a3a4b3ee4b14", "b1e6c7f6dbcb1010b5a9a3a4b3ee4b02"],
    sendEmail: function (userId, message) {
        // code to send an email
        gs.info("Email to " + userId + " sent!");
    },
    sendEmailToTeam: function (message) {
        this.teamMembers.forEach(function (teamMember) {
            // This will now work as expected
            this.sendEmail(teamMember, message);
        }, this);
    }
};
// Works!
user.sendEmailToTeam("Hey team!");
Success messages for the emails being sent are logged into the ServiceNow background script.

2. Object.keys()

In the same way forEach() makes for loops cleaner, Object.keys() makes for-in loops cleaner.

The Object.keys() method in JavaScript is a valuable tool that helps you grab an array containing all the property names, or “keys,” within an object. Because Object.keys() returns an array, you can use all the great Array methods on the result.

Object.keys() takes a single argument, the object.

Examples

Let’s assume we had an object whose keys were the sys_ids of Incidents. The value of each key will be the Short Description of the Incident.

var incidents = {
    "57af7aec73d423002728660c4cf6a71c": "Unable to access the shared folder.",
    "ed92e8d173d023002728660c4cf6a7bc": "Email server is down.",
    "e329de99731423002728660c4cf6a73c": "Defect tracking tool is down."
};

Let’s use Object.keys() to get an array of all the sys_ids.

gs.info(Object.keys(incidents));
The sys_ids of the Incident records are logged into the ServiceNow background script.

We can then take that array of sys_ids and use them in a GlideRecord “in” query, for example.

var incidentLookup = new GlideRecord("incident");
incidentLookup.addQuery("sys_id", "IN", Object.keys(incidents));
incidentLookup.query();
// NOTE: You should not use getRowCount in production. We are using it here to simplify the example.
gs.info(incidentLookup.getRowCount());
The number of incidents is logged into the ServiceNow background script.

What if you wanted to get the value associated with each sys_id, in this case, the Short Description of the Incident record? Let’s use our forEach() method from earlier.

// Object.keys() returns an array of sys_ids, the forEach() function loops over that array
// and the sys_id is passed into the callback of the forEach() function
// This will log the short description of each incident
Object.keys(incidents).forEach(function (incidentSysId) {
    // Using bracket notation, we can do property lookups on Objects
    // with variables that contain the property names as strings
    var incidentShortDescription = incidents[incidentSysId];

    gs.info(incidentShortDescription);
});
The short descriptions of the Incidents are logged into the ServiceNow background script.

The same principle applies even with more complex data structures.

Take the following expanded data structure. Instead of having the object’s value be the Short Description of the incident, let’s assume we’ve used objects to store data from a GlideRecord.

// Object keys are sys_ids of the record
// Values of Object are Objects that represent the Incident, containing
// the short description and number
var incidents = {
    "57af7aec73d423002728660c4cf6a71c": {
        shortDescription: "Unable to access the shared folder.",
        number: "INC0009009"
    },
    "ed92e8d173d023002728660c4cf6a7bc": {
        shortDescription: "Email server is down.",
        number: "INC0009005"
    },
    "e329de99731423002728660c4cf6a73c": {
        shortDescription: "Defect tracking tool is down.",
        number: "INC0009004"
    }
};

If we need to access the Incident objects as we loop over the array of keys, we can do that by using the sys_id to find the value of our incidents object, which would be the Incident data of Short Description and Number. This works because we can look up an object’s property by using bracket notation.

// Object.keys() returns an array of sys_ids, the forEach() function loops over that array
// and the sys_id is passed into the callback of the forEach() function
// This will log the short description and number of each incident
Object.keys(incidents).forEach(function (incidentSysId) {
    // Using bracket notation, we can do property lookups on Objects
    // with variables that contain the property names as strings
    var incident = incidents[incidentSysId];

    gs.info("The incident with a sys_id of " + incidentSysId + " has a number of " + incident.number + " and a short description of " + incident.shortDescription);
});
The Incident sys_ids, numbers, and short descriptions are logged into the ServiceNow background script.

As ServiceNow begins to support later versions of ECMAScript, Object.values() can streamline the Object.keys().forEach() process further.

3. [].map()

How often have you had one array, looped through it, and pushed some data to a new array? Doesn’t it get messy? Let’s look at a method that can help: map().

map() is a useful method in JavaScript that helps you create a new array by setting the element in the new array to the callback’s return value for each element in the original array. The best part? The original array remains unmodified!

Similar to forEach(), map() takes two arguments:

  1. callback: The function that will execute for each item in the array.

  2. thisArg: An object to use as this inside the callback.

Just like forEach(), each time the callback executes, it will receive the following three arguments:

  1. item: The item in the array at the current index.

  2. index: The zero-based index of the item.

  3. array: The entire array.

Examples

Let’s see what it would look like if we had an array of User sys_ids and wanted to build out user objects for each sys_id.

var userIds = ["62826bf03710200044e0bfc8bcbe5df1", "5137153cc611227c000bbd1bd8cd2005", "f298d2d2c611227b0106c6be7f154bc8"];

Now, let’s use the map() function to make a new array of Users.

var users = userIds.map(function (userSysId) {
    var user = new GlideRecord("sys_user");
    
    if (user.get(userSysId)) {
        return {
            sysId: userSysId,
            name: user.getValue("name")
        };
    }
});

gs.info(JSON.stringify(users, null, 2));
The data structure of the created array of user objects, including sys_id and name of each user, is logged into the ServiceNow background script.

In this example, we use map() to loop over our array of User sys_ids, perform a GlideRecord lookup of that user, and return an object containing the sys_id and name of the user. We’ll use JSON.stringify() to make the object easier to read in a background script.

What might be a time you run into this? Have you ever used a List field in ServiceNow?

The List field in ServiceNow returns all the sys_ids from the records in that List as a single comma-delimited string.

The value of a List field from a GlideRecord would look like "f298d2d2c611227b0106c6be7f154bc8, 62826bf03710200044e0bfc8bcbe5df1, 5137153cc611227c000bbd1bd8cd2005". Let’s apply this same concept to build an array of objects using the value off of a List field.

// Assume incident is a GlideRecord for an Incident record
var listOfUserIds = incident.getValue("watch_list");
gs.info("listOfUserIds: " + listOfUserIds);

// Split the array on commas to get an array
var userIds = listOfUserIds.split(",");

var users = userIds.map(function (userSysId) {
    var user = new GlideRecord("sys_user");
    
    if (user.get(userSysId)) {
        return {
            sysId: userSysId,
            name: user.getValue("name")
        };
    }
});

gs.info("users: " + JSON.stringify(users, null, 2));
A list of user sys_ids followed by the array of user objects with their sys_id and name is logged to the ServiceNow background script.

In this example, we took the string value from a List field, split it on the commas to get an array, and then used map() to perform User lookups and create an array of objects of User data.

Summary

As you can see, there are some potent JavaScript tools at our disposal in ServiceNow today. forEach(), Object.keys(), and map() are three helpful methods for processing data.

forEach() replaces the traditional for loop, allowing you to write cleaner loops.

Object.keys() provides a cleaner way to iterate over an object’s keys.

map() lets you transform arrays cleanly while leaving the original array intact.

These three methods have incredible synergy together and can help make your code more maintainable and easier to read. For example, you can use objects to dedupe lists of records in ServiceNow and leverage Object.keys() and forEach() or map() to easily build out a complete data structure.

Want to collaborate with us? Please get in touch.


Posted

in

,

by

Leave a Reply

Subscribe now to keep reading and get access to the full archive.

Continue reading