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.
Top comments (0)