Shahed Shah1
Tera Guru

So, you've spent a lot of time and drank a whole lot of coffee (yuck) and finally got your Client Script working. You know. That script that fetches values from another table and populates a field on your form using something like g_form.getReference or GlideRecord. Beaming with pride you present that script for a peer review which is shot down almost immediately. There's that feeling right?

find_real_file.png

So, what went wrong? The first usual suspect is whether the script itself "async". Queue a confused Kevin, again.

What does "Async" mean?

Let's get the first thing out of the way. Async is short for asynchronous. To understand let's think of how we program. Usually when we enter the code, it is entered as a block of statements in sequence, where each statement is executed after waiting for the previous statement to execute. Here, we get the impression of code executing in a "synchronous" nature.

With asynchronous code, the execution takes the code outside of the main flow of the program and is executed without waiting.

What Async means for the ServiceNow APIs

When using the g_form.getReference function or the GlideRecord API, you will notice that the developer docs describe a callback function. This makes the calls run asynchronously. To understand why this is a good thing, let's look at an example:

var grUsers = new GlideRecord('sys_user');
grUsers.addQuery('active', 'true');
grUsers.query();
while (grUsers.next()) {
   console.log(grUsers.getValue("name"));
}

The User [sys_user] table was chosen for the example as many companies can have a ginourmous amount of user records. If you were to add this to an onChange Client Script for any field (I'm not telling you to do this by the way), you will notice that the browser will briefly pause (is unresponsive) for a moment. Let's break down what's happening (from a high level):

  1. The browser is assembling a request to get all the active records from the user table
  2. The request is sent and the browser waits
  3. On the server-side, it receives the request and builds the query
  4. The query is executed and all the records are brought back to the server
  5. The results is assembled into an XML response and sent back
  6. The XML response is received by the client (the browser)
  7. This response (via the ServiceNow API) compiles the response into a JavaScript object
  8. With that done, the browser has the object in memory and is no longer waiting

That is a lot! And I didn't even go into all the specifics (especially network wait times). All this is being done and the browser waits for a response, which is why we experience what some describe as a "lock" where the browser is unresponsive for a moment. How long depends on the amount of data that is brought back and what is done with the records afterwards.

Here's how it looks for an almost out-of-the-box Instance

find_real_file.png

That green? That's how long the browser waited (2.78s).

So, how do we make this asynchronous? Add a callback. So I could change the above code to something along the lines of:

var grUsers = new GlideRecord('sys_user');
grUsers.addQuery('active', 'true');
grUsers.query(function(gr) {
  while (gr.next()) {
    console.log(gr.getValue("name"));
  }
});

The timings will be the same, but the browser and the user can get on with other things while the browser is waiting on the response.

So, what's the fight?

Okay. It's not really a fight. When you have read the Developer Documentation links I provided earlier (g_form.getReference and GlideRecord API), you will understand that the getReference is a short-cut to building up a GlideRecord request for a particular Reference field. However, I have seen that the GlideRecord API was used for the same purpose. But. But, there is a difference. More typing!

Let's look at an example of using getReference on the Assigned to field of an Incident:

find_real_file.png

What I will do is return the object into the console so that we can have a look at what we have.

g_form.getReference('assigned_to', function(gr) { 
  console.log(gr); 
});

inspecting the browser console shows

find_real_file.png

As you can see I expanded the arrow to see a list of fields and values. Essentially all the fields for that record have been returned. Even if you want just one field, you're still getting the entire record.

This is where the fight (or area of contention) comes in... this still occupies browser memory. If you go crazy will lots of async requests onLoad and onChange, especially if querying the same record multiple times, you're not doing the browser (even the user as a matter of fact) any favours. First off, the platform is handling the record collection and querying, then the browser stores that into memory.

Settling things

If you've read through all that and go this part in the hope of improving things, bravo! So what can you do to improve the experience for the users?

Here's a quick list of docs that would be a good starting point:

Try maximising the usage of the g_form.getReference function as the results are cached. If the a request was already made and a record already exists in the local cache, the platform will skip making a request and use the cached result instead.

Did you know that the g_scratchpad object is still available in the client side? Here's an idea. If you have retrieved a record, how about serialising it to the g_scratchpad? If you are about to retrieve the same record again, you can check in g_scratchpad first. It probably wasn't designed for that purpose, but hey, there's a way to "cache" a result. 

For the obsessive

Now, you're probably thinking: what else can I do to minimise on the memory usage? Here's a little tidbit I worked on for a colleague as a proof of concept. Using GlideAjax to act like a GlideRecord/getReference, but to get one field value only. It's probably overkill, but it still convinced them of the benefits of using GlideAjax.

The first step was to create a Script Include that does the work:

var GetFieldValueAjax = Class.create();
GetFieldValueAjax.prototype = Object.extendsObject(AbstractAjaxProcessor, {

	getFieldValue: function() {
		var tableName = this.getParameter("sysparm_table_name"),
			sys_id = this.getParameter("sysparm_sys_id"),
			fieldName = this.getParameter("sysparm_field_name"),
			fieldValue = "";
		
		var gr = new GlideRecord(tableName);
		if (gr.get(sys_id)) {
			// Try to get the display value in case it's a date/time, reference field, etc
			fieldValue = gr.getDisplayValue(fieldName); 
		}
		
		return fieldValue;
	},

    type: 'GetFieldValueAjax'
});

Then, let's create a helper function to call this Script Include. Now I decided to create this as a global UI Script to show that the helper function can be used anywhere: 

function getFieldNameClient(sourceField, fieldName, callback) {
  var sourceFieldValue = g_form.getValue(sourceField), 
    sourceFieldTable = g_form.getGlideUIElement(sourceField).reference,
    ga = new GlideAjax('GetFieldValueAjax');
	
  ga.addParam('sysparm_name', 'getFieldValue');        // The Script Include function
  ga.addParam('sysparm_sys_id', sourceFieldValue);     // Target record's Sys ID
  ga.addParam('sysparm_table_name', sourceFieldTable); // Target record's table
  ga.addParam('sysparm_field_name', fieldName);        // What field value is wanted
  ga.getXML(function(response) {
    var answer = response.responseXML.documentElement.getAttribute("answer");
    callback(answer);
  });
}

Barring the lack of validation and coding style, you can observe that you pass in your callback function to do something with the result. So, it would get called like this in the Client Script

getFieldNameClient('caller_id', 'name', function(res) { 
  console.log(res); 
});

In this example, caller_id is the field on the form we are currently on and the helper function will pick it up to find out what table to query and what Sys ID is needed, and name is the field value we want to get for the record. The final parameter is a function to do something with the response from GlideAjax, in this case it's outputting the results to the browser console.

For proof that it works (despite the non-existent testing) and coz I love screenshots...

find_real_file.png

Of course, for the heavy coders out there, there's many ways to improve on this (validation, add it to the g_form prototype, whatever). But I do hope that this demonstrates the usefulness of using the GlideAjax API.

Summary

So, there you have it. Along the way we have seen how utilising async code can improve the end user experience. Then, to further enhance the experience, looked at ways to minimise how often to do this. I have attached an Update Set for the "obsessive" example for you to peruse and do what you want with it.

Have you discovered anything else of use to improve on the user experience in Client Scripts?