Sam

Sam's ServiceNow adventures

A place where to share about ServiceNow platform and technologies

High order function in script include, accessing the context (this)

how to access this context when using high order functions in a ServiceNow script include.

Sam

5 minute read

In the Service-Now script includes (or in Javascript in general), the context of the this in not reachable when using high order function.

This might be obvious for some of you, but it was not for me and I struggled some time to achieve what I wanted in a clean manner. Hence this post.

You are maybe wondering what are high order functions? Well this can be defined as follow :

Functions that operate on other functions, either by taking them as arguments or by returning them, are called higher-order functions.

-- Marijn Haverbeke, Eloquent JavaScript, 3rd edition

Practically this is what is used when using for example call-back functions or whenever you are passing a function as an argument to another function.

When doing so, this refers to the context. In Service-Now script include, it is typically used to reference global variables, that are accessible by all the functions within the script include, and to call other functions inside of the script include.

common usage of this

For example, in the following code we are use this to access the variable setInit and to call the function myOtherFunction.

var test = Class.create();
test.prototype = {
    initialize: function() {
		this.setInit= "Set in init"; 
    },
	
	myFunction : function (){
		gs.info("value of setInit in myfunction: " + this.setInit);
		this.myOtherFunction();
	},
	myOtherFunction : function (callBack){
		gs.info("value of setInit in myOtherFunction: " + this.setInit);
	},
    type: 'test'
};

this context is lost when calling a function given as a parameter to another function

Now let’s imagine with we need to pass a function as an argument to another function:

	 initialize: function() {
		this.setInit= "Set in init";
     },

	doFunction : function (){
		gs.info("value of setInit in doFunction: " + this.setInit);
		this.processFunction(this.postProcessFunction);
		
	},
	processFunction : function (callBack){
		gs.info("value of setInit in processFunction: " + this.setInit);
		postProcessFunction("from processfunction");
	},
	postProcessFunction : function (text){
        // now this. is not referencing to the global context of the script inclue
		gs.info("value of setInit in processFunction: " + this.setInit); // can not access the value defined
		this.doSomethingElseFunction ("fromProcessfunction"); // can not call this function
	},
	doSomethingElseFunction : function (text) {
        gs.info(text);
    }

In order to solve this, we need to indicate that we want that the function postProcessFunction provided as an argument to processFunction has its thiskeyword set to the correct value, which is the global context.

Adding the bind method to bound the context to the function

To do so, we need to add .bind method when passing the function as a parameter to processFunction, with the current context which is this :

 	initialize: function() {
		this.setInit= "Set in init";
     },

	doFunction : function (){
		gs.info("value of setInit in doFunction: " + this.setInit);
		// Here we add the .bind method, with this as an argument
		this.processFunction(this.postProcessFunction.bind(this)); 
		
	},
	processFunction : function (callBack){
		gs.info("value of setInit in processFunction: " + this.setInit);
		postProcessFunction("from processfunction");
	},
	postProcessFunction : function (text){
        // now this. IS referencing to the global context of the script inclue
		gs.info("value of setInit in processFunction: " + this.setInit); // CAN access the value defined
		this.doSomethingElseFunction ("fromProcessfunction"); // CAN call this function
	},
	doSomethingElseFunction : function (text) {
        gs.info(text);
    }

Real life usage example

You may wonder what are real world use cases, so let me describe you one situation where I used this.

I needed to call REST messages for various interfaces. The bulk of the REST messages call is common to all, just the message name and the actions to perform after a successful execution or after an error where different.

To do so, I used functions as in simplified example below.

The function called to trigger the interface is getUserDetails. It then call the executeRestMessage with the REST message name and the post processing function and the error processing function as parameter. Note there the bind used when passing the function as a parameter.

The in the real case, there is multiple functions similar as getUserDetails and the logic is a bit more complexe.

	  /**
      	@name getUserDetails
      	@description Trigger the integration for getUserDetails
     */
    getUserDetails : function () {
        
        // call the execute MEssage for this interface and with the function. Note the "bind" used.
        this.executeMessage("users", "getUserDetails", this.getUserDetails_postProcess.bind(this), this.getUserDetails_errorProcess.bind(this));
    },
	/**
		@name executeRestMessage
		@description Execute the given REST message.If provided add string to endpoint and add query paramters. Then execute the given post process function.
		@param {String} [messageName] - Name of the REST message
		@param {String} [methodName] - Name of the http method 
		@param {function} {postProcessFunction} - Function to execution afer sucessfull execution of the REST Message
		@param {function} [errorProcessFunction] - Function to execution afer error in execution of the REST Message
	*/
	executeRestMessage : function (messageName, methodName, postProcessFunction, errorProcessFunction){
        // get REST message
        var message = new sn_ws.RESTMessageV2(messageName, methodName);
        
        // execute message
         var response = message.execute();
        
        // get http status from the response
        var httpStatus = response.getStatusCode();
               
        // in case of success, call the postProcessFunction
        if (httpStatus == '200'){
            postProcessFunction(response.getBody());
        } else {
            // or in case of error, call errorProcessFunction
            errorProcessFunction(response.getBody());
        }
    },
     /**
      	@name getUserDetails_postProcess
      	@description Post processing for getUserDetails
      	@param {String} [responseBody] - Response body from REST execution
     */
	getUserDetails_postProcess (responseBody){
        // do post processing, update record, etc. "this" is accessible if needed
        
    }, 
    /**
      	@name getUserDetails_errorProcess
      	@description Error processing for getUserDetails
      	@param {String} [responseBody] - Response body from REST execution
     */
    getUserDetails_errorProcess(responseBody){
        // do error processing (create incident, log message, etc..). "this" is accessible if needed
    }    

I hope you find this information and this example usefull. Please fell free to leave any message or to open a discussion on this topic in the comments below.

References

  1. Marijn Haverbeke, Eloquent JavaScript, 3rd edition,https://eloquentjavascript.net/Eloquent_JavaScript.pdf, 2018
  2. Mozilla, MDM web docs, Function.prototype.bind(), https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind, 2020 04 29
  3. Alex Devro, Blog, How “this” in javaScript work, https://blog.alexdevero.com/this-in-javascript-works/, 2020 04 20

Subscribe to my newsletter

Say something

Comments powered by Talkyard.

Recent posts

Categories

About

This blog is a personnal blog from Samuel Meylan about ServiceNow technologies and other business related topics.

Sam is a ServiceNow Senior Developper and technical consultant with more than 8 years of experience. He particularly like making integrations and solving complexes requirements.

He also enjoy discovering new and enhanced features shipped with each ServiceNow release.