DEV Community

Bruce Axtens
Bruce Axtens

Posted on

Running Google Apps Script functions in the background, Part 2

In part 1, I alluded to the possibility of running things more frequently than once per minute. Here's the code, with some discussion.

function onTimer() {
    // create four timers for 15, 30, 45 and 60 seconds hence
    const scpt = new ScptProps();

    const lock = LockService.getScriptLock();
    lock.waitLock(30000);

    if (null === scpt.get("seconds15")) {
        const seconds15 = ScriptApp.newTrigger("ProcessFunctionQueue")
            .timeBased()
            .after(15 * 1000)
            .create();
        scpt.set("seconds15", seconds15.getUniqueId());
    }

    if (null === scpt.get("seconds30")) {
        const seconds30 = ScriptApp.newTrigger("ProcessFunctionQueue")
            .timeBased()
            .after(30 * 1000)
            .create();
        scpt.set("seconds30", seconds30.getUniqueId());
    }

    if (null === scpt.get("seconds45")) {
        const seconds45 = ScriptApp.newTrigger("ProcessFunctionQueue")
            .timeBased()
            .after(45 * 1000)
            .create();
        scpt.set("seconds45", seconds45.getUniqueId());
    }

    if (null === scpt.get("seconds60")) {
        const seconds60 = ScriptApp.newTrigger("ProcessFunctionQueue")
            .timeBased()
            .after(60 * 1000)
            .create();
        scpt.set("seconds60", seconds60.getUniqueId());
    }

    lock.releaseLock();
}
Enter fullscreen mode Exit fullscreen mode

So there's the updated onTimer which gets run every minute via a Time-Dependent trigger.

In there we create four installable triggers and store their uniqueIds in script properties. The reason for this is that installable triggers are a limited resource. Even after they've done what they were created to do, they don't slip out of existence. Rather they hang around until removed. In the ProcessFunctionQueue function is the call to a function to remove completed triggers.

I'm not entirely sure about the LockService.getScriptLock() stuff. I only read about it yesterday but it's in there JIC.

function ProcessFunctionQueue(json: any) {
    ClearUsedTrigger(json.triggerUid.toString())
    const scpt = new ScptProps();
    let funcQueueTxt = scpt.get("FUNCTION_QUEUE");
    if (funcQueueTxt === "[null]" || funcQueueTxt === null) {
        funcQueueTxt = "[]";
    }
    const functionQueueJsn = JSON.parse(funcQueueTxt);
    if (functionQueueJsn.length > 0) {
        const functionQueueItm = functionQueueJsn.pop();
        scpt.set("FUNCTION_QUEUE", JSON.stringify(functionQueueJsn));
        eval(functionQueueItm + "()");
    }
}
Enter fullscreen mode Exit fullscreen mode

When the installable trigger executes, it passes a blob of json to the called function which contains the triggerUid. This id is passed on to the ClearUsedTrigger function. Then a function name is popped off the FUNCTION_QUEUE and eval-ed.

It's a this point that I wonder about locks and buried updates. I hope someone enlightens me about that sooner than later.

function ClearUsedTrigger(id: string) {
    const scpt = new ScptProps();
    ScriptApp.getProjectTriggers().forEach(trigger => {
        if (trigger.getEventType() == ScriptApp.EventType.CLOCK) {
            if (trigger.getUniqueId() === id) {
                ScriptApp.deleteTrigger(trigger);
                ["seconds15", "seconds30", "seconds45", "seconds60"].forEach(itm => {
                    if (scpt.get(itm) === id) {
                        scpt.forget(itm);
                    }
                });
            }
        }
    });
}
Enter fullscreen mode Exit fullscreen mode

This bit of magic iterates through the project triggers, finds CLOCK events, and finds which of the seconds* script properties matches the id and forgets that property.

The AddToFunctionQueue remains the same

function AddToFunctionQueue(fn: string) {
    const scpt = new ScptProps();
    let funcQueueTxt = scpt.get("FUNCTION_QUEUE");
    if (funcQueueTxt === null || funcQueueTxt === "[null]") {
        funcQueueTxt = "[]";
    }
    let funcQueueJsn = JSON.parse(funcQueueTxt);
    if (funcQueueJsn.indexOf(fn) === -1) {
        funcQueueJsn.unshift(fn);
    }
    scpt.set("FUNCTION_QUEUE", JSON.stringify(funcQueueJsn));
}

Enter fullscreen mode Exit fullscreen mode

Again, I wonder if that's also a place where a lock would be useful.

Bottom line: I'm still trying to get my head around Google Apps Script. Some things are obvious, some not. If you end up adapting anything from the above do let me know.

Top comments (0)