Sunday 5 December 2021

JavaScript for Apex Programmers Part 2 - Methods and Functions


(for the full Gertrude Stein quote on art having no function and not being necessary, see : https://www.scottedelman.com/2012/04/26/art-has-no-function-it-is-not-necessary/)

Introduction

In the first part of this occasional series on JavaScript for Apex Programmers, we looked at my chequered history with JavaScript, then the difference between Apex and JavaScript types. In this instalment we'll look at methods and functions.

Definitions

A method is associated with an object. It's either part of an object instance (non-static) or part of the class that objects are created from (static method). A function is an independent collection of code that can be called by name from anywhere. These are often used interchangeably (by me especially!) but the difference is important when considering JavaScript and Apex.

Apex

Apex only has methods. All Apex code called by its name is either a method being invoked on an object instance (that you or the platform created) or a static method in a class.

...

Almost all Apex code.

...

Apart from triggers. 

...

Here's an account trigger with a named collection of code that I can call later in the trigger:

trigger AccountMethod on Account (before insert) 
{
    void logMessage(String message) 
    {
        System.debug('Message : ' + message);
    }
    
    logMessage('In account trigger');
}

And if I execute anonymous and insert a trigger, I see the message appear as expected:



While this might look like a function, it's actually a static method on the trigger, as I found out by changing the use to this.logMessage('In account trigger');



It's not a useful static method though, as it can't be called from outside of this trigger. I suppose it could be used to organise code in the trigger to be more readable, but code shouldn't live in triggers so you'd do far better to put it in a utility or service class.

That interesting digression over with, as far as we are concerned Apex has methods in objects or classes, and I'll proceed on that basis.

If you want to pass some Apex code to be executed by another method, you have to pass the object to the method. An example of this that we come across pretty often is scheduled Apex:

MySchedulable mySch = new MySchedulable();
String cronStr = '21 00 9 9 1 ?';
String jobID = System.schedule('My Job', cronStr, mySch);
The platform will call the execute() method on the mySch instance of the MySchedulable class that I pass to it.

 

JavaScript

JavaScript, as I find so often the case, is a lot more powerful and a lot more confusing. JavaScript has both methods and functions, but under the hood methods are functions stored as a property of an object. 

Functions are also actually object instances - every function you create is actually an instance of the Function object.  Which means they can have properties and methods like other objects. And those methods are actually functions stored as properties. And so on.

The good news is that you don't need to care about most of this when using JavaScript in Salesforce. In my experience, what you really need know is:

Functions are First Class Citizens

In JavaScript, functions can be assigned to variables:

let log=function(message) { 
                console.log(message);
        }
   
log('Hello');

Hello

passed as parameters to other functions:

let consoleLogger=function(message) {
               console.log(message);
               }
               
let log=function(logger, message) {
             logger(message);
}

log(consoleLogger, 'Message to console');

Message to console

and returned as the result of a function;

function getConsoleLogger() {
    return function(message) {
        console.log(message);
    }
}

let consoleLogger=getConsoleLogger();

consoleLogger('Message for console');

Message for console

Functions can be Anonymous

When you create callback functions in JavaScript for very simple, often one-off use, they quickly start to proliferate and become difficult to distinguish from each other. Anonymous functions are defined where they are needed/used and don't become a reusable part of the application. Using a very simplistic example, for some reason I want to process an array of numbers and multiply each entry by itself each entry. I'm going to use the map() method from the Array object, which creates a new array by executing a function I supply on every element in the source array. If I do this with named functions:

function multiply(value) {
    return value*value;
}
let numbers=[1, 2, 3, 4];
let squared=numbers.map(multiply);
console.log(squared);
[1, 4, 9, 16]

If I don't need the multiple function anywhere else, it's being exposed for no good reason, so I can replace it with an anonymous function that I define when invoking the map method:

let numbers=[2, 4, 6, 8];
let squared=numbers.map(function(value){return value * value});
console.log(squared);
[4, 16, 36, 64]

My anonymous function has no name and cannot be used anywhere else. It's also really hard to debug if you have a bunch of anonymous functions in your stack, so exercise a little caution when using them.

Arrow Functions improve on Anonymous

Especially for simple functions. Arrow functions (sometimes called fat arrow functions) give you a more succinct way to create anonymous functions.

numbers.map(function(value){return value * value});
I can lose a lot of the boilerplate text and just write:
numbers.map(value=>value*value);

Breaking this down:

  • I don't need the function keyword - I replace it with =>
  • I don't need parenthesis around my parameter, I just put it to the left of =>
    Note that if I have no parameters, or more than one, I do need parenthesis
  • I don't need the braces, as long as the code fits onto a single line
  • I don't need the return statement, again as long as the code fits onto a single line. The result of my expression on the right hand side of => is implicitly returned
Thus arrow functions can look pretty different to regular functions:
let multiply=function(value) {
    return value * value;
}

let arrowMultiply=value=>value*value;

or quite similar

let addAndLog=function(first, second) {
    let result=first + second;
    console.log('Result = ' + result);
    return result;
}

let arrowAddAndLog=(first, second)=>{
    let result=first + second;
    console.log('Result = ' + result);
    return result;
}

Arrow functions have a few gotchas too - the major one is 'this' always refers to the Window object, regardless of how you might try to change it. 

Functions have a Context

There's quite a bit to this (pun intended!) and as I mentioned in the first instalment, this isn't intended to be a JavaScript tutorial, so I can't see any point in replicating the Mozilla Developer Network content. Instead I'll just point you at it. The key thing to remember is 'this' depends on how the function is called, not where it is declared, so if you pass an object method as a callback function, when it is invoked 'this' won't refer to the original object, but whatever object is now invoking it. I'd recommend spending some time getting to grips with the context, otherwise you'll likely spend a lot more time trying to figure out why things don't work.

Related Posts

JavaScript for Apex Programmers Part 1 - Typing

No comments:

Post a Comment