Running Google Apps Script functions in the background, Part 2
Tags: javascript, googleappsscript, googlesheets
Published: 2019-09-13 06:40:00

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

```typescript
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](https://www.internetslang.com/JIC-meaning-definition.asp).

```typescript
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.
```typescript
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
```typescript
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.