loading...

Running Google Apps Script functions in the background, Part 2

bugmagnet profile image Bruce Axtens ・2 min read

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();
}

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 + "()");
    }
}

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);
                    }
                });
            }
        }
    });
}

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));
}

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.

Posted on by:

bugmagnet profile

Bruce Axtens

@bugmagnet

Programmed Canon Canola calculators in 1977. Assorted platforms and languages ever since. Assisting with HOPL.info. I am NOT looking for work -- I've got more than enough to do.

Discussion

pic
Editor guide