How to use loops with server-side JavaScript in Salesforce Marketing Cloud

How to use loops with server-side JavaScript in Salesforce Marketing Cloud

This article explains how to use loops with server-side JavaScript in Salesforce Marketing Cloud.

What are loops and how to use them

Loops are an essential part of any programming language. They allow us to go through a list of items, perform an action for a limited number of times, or try something until we achieve a valid result.

For example, we can loop through a list of subscribers and unsubscribe those who didn’t consent to being contacted by email.

Or, for instance, we can ask a server if an Automation has finished running every 10 seconds until the server replies positively.

Which loops can we use in SSJS?

In modern JavaScript, there are many methods to perform a loop. But, considering that server-side JavaScript is based on ECMAScript 3 standard released in 1999, most of those methods are not available in Marketing Cloud out-of-the-box.

Fortunately for us, the loops available to us are more than enough to perform any task we could possibly need.

How to use loops

This article covers 4 loop methods: FOR, FOR…IN, WHILE and DO…WHILE.

Please mind that all of the snippets below until Measuring the Performance chapter should be wrapped inside the following code:

<script runat='server'>

    Platform.Load('core', '1');

    HTTPHeader.SetValue("Content-Type", "application/json");

    try {
		
		/////////////////////////
		//// YOUR CODE GOES HERE
		/////////////////////////
		
	} catch(error) {

        Write(Stringify(error));

    }
	
</script>

FOR loops

FOR loops are most commonly used to run through a list of items (arrays or arrays of objects).

It runs for a limited number of times or until there are no more items to run through.

There are 2 types of FOR loop that work in Marketing Cloud: FOR and FOR…IN.

FOR loop

The FOR loop is perfect for working with simple Array objects.

var fruits = ["Apple", "Orange", "Pear", "Blackberry"],
    result = [];

for(var i = 0; i < fruits.length; i++) {
    var fruit = fruits[i];
    result.push(fruit);
}

Write(Stringify(result));

But it can also run for a predefined number of times when need be.

var result = [];

for(var i = 0; i < 2; i++) {
    result.push(i);
}

Write(Stringify(result));

It’s also possible, although not recommended, to break the FOR loop before it ends running, by setting the iterator to the final value.

var fruits = ["Apple", "Orange", "Pear", "Blackberry"],
    result = [];

for(var i = 0; i < fruits.length; i++) {
    var fruit = fruits[i];
    result.push(fruit);
    if(i == 2) i = fruits.length;
}

Write(Stringify(result));

Breaking the loop is particularly useful when there is no need to run through the whole scope once our goal is achieved.

FOR…IN loop

FOR..IN loops are best suited for running through an array of objects.

var fruits = [
        {
            Name: "Apple",
            Type: "Fruit"
        },
        {
            Name: "Orange",
            Type: "Fruit"
        },
        {
            Name: "Pear",
            Type: "Fruit"
        },
        {
            Name: "Blackberry",
            Type: "Fruit"
        }
    ],
    result = [];

for(var k in fruits) {
    var fruit = fruits[k];
    result.push(fruit.Name);
}

Write(Stringify(result));

Considering that the SSJS functions mainly return arrays of objects, the FOR…IN loop is the loop we use the most.

WHILE loops

WHILE loops can run pieces of code indefinitely until the condition is set from true to false.

There are 2 types of WHILE loop: WHILE and DO…WHILE.

WHILE loop

The basic WHILE loop first verifies the condition, then runs the code. Therefore, if the initial condition is set to false, the WHILE loop will be skipped.

var fruits = ["Apple", "Orange", "Pear", "Blackberry"],
    result = [],
    i = 0;

while(fruits[i] != null) {
    result.push(fruits[i]);
    i++;
}

Write(Stringify(result));

Similar to the FOR loop, it is also possible to run a WHILE loop for a predefined number of times.

Please consider that in order to use an iterator instead of a condition, we need to iterate in reverse. This means that we need to reverse back the order of items before returning the results.

var fruits = ["Apple", "Orange", "Pear", "Blackberry"],
    quantity = fruits.length,
    result = [];

while(quantity--) {
    result.push(fruits[quantity]);
}

result.reverse();

Write(Stringify(result));

But unlike the FOR loop, the WHILE loop has a built-in break-out method that stops the loop immediately.

var fruits = ["Apple", "Orange", "Pear", "Blackberry"],
    result = [],
    i = 0;

while(fruits[i] != null) {
    result.push(fruits[i]);
    i++;
    if(i == 2) break;
}

Write(Stringify(result));

DO…WHILE loop

DO…WHILE loop works exactly like the regular WHILE loop but it is guaranteed that the code will run at least once before the condition is verified.

var fruits = ["Apple", "Orange", "Pear", "Blackberry"],
    result = [],
    i = 0;

do {
	
	result.push(fruits[i]);
    i++;
	
} while(fruits[i] != null);

Write(Stringify(result));

Breaking the loop comes very handy when looking for a particular value or setting a time-out for the loop.

For example, we are all aware that Script Activities in the Automation Studio time-out after 30 minutes, but by using the code below we can stop our script running beyond 25 minutes, thus giving it 5 minutes to complete all the other processes before coming to an end.

var now = new Date(),
    start = now.getTime(),
    timeout = 1500000; // this is 25 minutes in milliseconds

var result = [],
    i = 0;

do {

    /* DO SOMETHING */

} while((new Date().getTime() - start) < timeOut);

Infinite loops and performance killers

There are 2 main things that can go wrong when using loops in JavaScript: they can run indefinitely and kill your browser, or they can gradually loose in performance, making the loop run for an extended period of time.

Infinite loops

Infinite loops are in most cases the result of a poorly written WHILE loop: they happen when the condition of the loop is never set to false.

var i = 0;

while(i < 0) {
    i++;
}

Performance killers

Functions that cause a degradation in performance over time are not easy to single-out. A perfect example would be the SHIFT function that inserts a new value in the beginning of an Array object.

var fruits = ["Apple", "Orange", "Pear", "Blackberry"],
    result = [],
    i = 0;

while(fruits[i] != null) {
    result.unshift(fruits[i]);
    i++;
}

Write(Stringify(result));

As we can see on the chart below, the longer the loop runs, the less efficient it gets.

This is what we call a performance killer! And it should always be avoided when working with large sets of data.

Measuring the performance

Now, let’s have some fun! The SSJS script below generates an array of 10000 objects, then runs through it using every conditional loop we have covered in this article and measures the time it takes to complete.

<script runat='server'>

    Platform.Load('core', '1');

    HTTPHeader.SetValue("Content-Type", "application/json");

    var api = new Script.Util.WSProxy();

    var results = {};

    try {

        var db = db(10000);

        Write(Stringify({
            forLoop: forLoop(db),
            whileLoop: whileLoop(db),
            whileBreakLoop: whileBreakLoop(db, 500),
            whileLoopCached: whileLoopCached(db),
            doWhileLoop: doWhileLoop(db),
            forInLoop: forInLoop(db)
        }));

    } catch(err) {

        Write(Stringify(err));

    }

    function whileLoopCached(arr) {

        var start_time = new Date(),
            result = [],
            len = arr.length;

        while(len--) {

            result.push(arr[len]);

        }

        result.reverse();

        var end_time = new Date(),
            elapsed_time = (end_time - start_time) / 1000;

        return {
            time: elapsed_time,
            count: result.length
        }

    }

    function forInLoop(arr) {

        var start_time = new Date(),
            result = [];

        for(var k in arr) {
            result.push(arr[k]);
        }

        var end_time = new Date();

        return {
            time: (end_time - start_time) / 1000,
            count: result.length
        }

    }

    function whileLoop(arr) {

        var start_time = new Date(),
            result = [],
            i = 0;

        while(arr[i]) {

            result.push(arr[i]);

            i++;

        }

        var end_time = new Date(),
            elapsed_time = (end_time - start_time) / 1000;

        return {
            time: elapsed_time,
            count: result.length
        }

    }

    function whileBreakLoop(arr, max) {

        var start_time = new Date(),
            result = [],
            i = 0;

        while(i < arr.length) {

            result.push(arr[i]);

            if(i == max) break;

            i++;

        }

        var end_time = new Date(),
            elapsed_time = (end_time - start_time) / 1000;

        return {
            time: elapsed_time,
            count: result.length
        }

    }

    function doWhileLoop(arr) {

        var start_time = new Date(),
            result = [],
            len = arr.length,
            i = 0;

        do {

            var item = arr[i];
            i++;
            result.push(item);

        } while(i < len);

        var end_time = new Date();

        return {
            time: (end_time - start_time) / 1000,
            count: result.length
        }

    }

    function forLoop(arr) {

        var result = [];

        var start_time = new Date(),
            result = [],
            len = arr.length;

        for(var i = 0; i < len; i++) {
            result.push(arr[i]);
        }

        var end_time = new Date();

        return {
            time: (end_time - start_time) / 1000,
            count: result.length
        }

    }

    function db(n) {

        var result = [];

        var n = n || 10000;

        for(var i = 0; i < n; i++) {

            var o = {
                id: i,
                name: "Item #" + i,
                category: "item",
                CreatedDate: Now()
            }

            result.push(o);

        }

        return result;

    }

</script>

Please, feel free to run it and measure yourself how much time it takes for each loop to execute.

Conclusion

Loops are important, but use them wisely: make sure to get rid of any performance killers and pay a close attention to any WHILE loops that could run indefinitely.

Have I missed anything?

Please poke me with a sharp comment below or use the contact form.

Leave a Reply

Your email address will not be published.

server-side Javascript
Up Next:

How to create a Data Extension with Data Retention options using SSJS

How to create a Data Extension with Data Retention options using SSJS