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.
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 this
keyword 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
- Marijn Haverbeke, Eloquent JavaScript, 3rd edition, https://eloquentjavascript.net/Eloquent_JavaScript.pdf, 2018
- Mozilla, MDM web docs, Function.prototype.bind(), https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind, 2020 04 29
- Alex Devro, Blog, How “this” in javaScript work, https://blog.alexdevero.com/this-in-javascript-works/, 2020 04 20
Comments powered by Talkyard.