GAS - Execution time-out - google-apps-script

I have a spreadsheet that needs to 'setValue' to about 2000 rows.
The problem is, after awhile, I get time-out.
So I'm looking for a solution to split up the job.
I'm thinking of creating a time-based trigger, possibly to start off where the last row or timeout ended. But I can't figure how to get trigger unique ID.
function triggerList() {
ScriptApp.newTrigger('populateList')
.timeBased()
.after(5 * 60 * 1000) // run every 5 mins. Script will time-out.
.create();
}
function deleteTrigger() {
// delete the above trigger
var triggers = ScriptApp.getProjectTriggers();
for (var i = 0; i < triggers.length; i++) {
Logger.log(triggers[i]);
}
}
There is a similar topic on this but i don't quite understand.What happens when I "sleep" in GAS ? (execution time limit workaround)
Anyone has suggestions, workarounds?

Put all of your data into a two dimensional array first, then set all the values at once. Each inner array is a row, each element of each inner array is a cell in a new column.
Set Values Documentation

Related

Terminate last execution using script

I have been testing a script for nearly 24 hours, the idea was that if certain cells were edited within the sheet, then the trigger would wait 5 minutes before resetting the values within those cells.
However when testing it became apparent that with many people using this sheet, the trigger was executing many times and sometimes the data edited by a user would reset within seconds because of a earlier execution.
To combat this I thought of putting a few lines of script in there, to cancel any previous executions of this function that is still running.
However with being a newbie to script and not having the knowledge I hope to gain, finding the information to start that script is proving difficult.
This is what I have so far
function RESET(e) {
const ar = e.range;
const as = ar.getSheet();
const cells = ["B3","E3"]; // add cells you want to be edited to activate the script
// if C3 or E3 is edited, wait for 300 seconds
if (as.getName()=="Gauge" && cells.includes(ar.getA1Notation())){
Utilities.sleep(300000);
as.getRange("B3").setValue("ALL");
as.getRange("E3").setValue("ALL");
}
}
You can use a document lock]1 to guard the RESET function running at the same time for multiple users, but this will probably lead to timeouts.
A better approach would be to create a time-driven trigger with a delay of 5 minutes inside the RESET function. When it fires, clear the values in B3/E3 or do any other work required.
Make sure that you clear previous triggers before you create new one, so that at any time only single trigger is active. If there is no user activity for 5 minutes, it will eventually run.
const SHEET = "Gauge"
// make sure to create as onEdit installable trigger, not a simple trigger
function onInstallableEdit(e) {
const ar = e.range;
const as = ar.getSheet();
const cells = ["B3","E3"]; // add cells you want to be edited to activate the script
// if C3 or E3 is edited, wait for 300 seconds
if (as.getName()==SHEET && cells.includes(ar.getA1Notation())){
const lock = LockService.getDocumentLock()
try{
lock.waitLock(10000)
// delete previous triggers, if any
ScriptApp.getProjectTriggers().filter(t=>t.getHandlerFunction()===myHandler.name).forEach(t=>ScriptApp.deleteTrigger(t))
// create new trigger to be fired after 5 minutes
ScriptApp.newTrigger(myHandler.name).timeBased().after(5*60*1000).create()
} catch(e){
console.warn(e)
}
}
}
function myHandler(){
const as = SpreadsheetApp.getActive().getSheetByName(SHEET)
as.getRange("B3").setValue("ALL");
as.getRange("E3").setValue("ALL");
}

How do I call google script triggers every second?

Real-life problem is that I want to call api every X seconds and can't seem to accomplish it using Google scripts.
As I know you can call trigger in every 1, 5, 10, 15 or 30 minutes.
Is there a way to run Google script every second?
function createTimeDrivenTriggers() {
ScriptApp.newTrigger('myFunction')
.timeBased()
.everyMinutes(1) //I can not write 0.01 instead of 1 here.
.create();
}
function myFunction() {
Logger.log("Logging");
}
createTimeDrivenTriggers();
Time-based triggers:
You could use the method after(durationMilliseconds) to call your function after a specified number of milliseconds, if you create the trigger at the end of your function, like this:
function myFunction() {
Logger.log("Logging");
ScriptApp.newTrigger("myFunction")
.timeBased()
.after(1000 * X) // Fire "myFunction" after X seconds
.create();
}
But it seems this method (at least currently) cannot be used for firing a function after much less than a minute, as you can see in this Issue Tracker case.
Apart from this, there is no time-based trigger that can be used for your purpose.
Workaround:
Another option, if you just want to perform some actions after X seconds, would be to use Utilities.sleep(milliseconds) and a for loop.
Of course, you would eventually reach Apps Script execution time limit, and for this reason you should:
Find out how many iterations you can make before reaching this limit.
Make each execution make this number of iterations.
Create a trigger that will fire your function after a certain amount of time, with after(durationMilliseconds).
It could be something similar to this:
function myFunction() {
var numIterations = 10; // Number of iterations before reaching time limit (change accordingly)
for (var i = 0; i < numIterations; i++) {
// Your actions
Utilities.sleep(1000 * X); // Pause execution for X seconds
}
ScriptApp.newTrigger("myFunction")
.timeBased()
.after(1000 * Y) // Fire "myFunction" after Y seconds
.create();
}
This way, you can keep your desired frecuency most of the time, and only when the execution time limit is reached (every 6 or every 30 minutes, depending on your account) this frecuency will once be lower (around 1 minute), before going back to the desired frecuency. It's not the behaviour you expect, but I think it's the closest you can get to it.
Reference:
after(durationMilliseconds)
Issue Tracker: Time based trigger could not fire after few seconds
Utilities.sleep(milliseconds)

Google Sheets Date Stamp Script not working [duplicate]

I have a Google spreadsheet where I would like to automatically update one field every time a new row is added by a Zapier automation.
Every time I test the script manually by adding a row or making edits on existing rows, the script fires as expected. However, when Zapier adds a new row to the spreadsheet absolutely nothing happens.
Any idea why and how I can remedy this problem?
This the script which I am trying to use:
function onEdit(e) {
var row = e.range.getRow();
SpreadsheetApp.getActiveSheet().
getRange(row, 10).setValue('My_New_Value');
}
I was able to replicate the behavior you are seeing in your script. I believe that when you add a row outside of the active data range, it does not trigger an edit event. In order to catch those events, you will also need to catch chagne events.
https://developers.google.com/apps-script/reference/script/spreadsheet-trigger-builder#onchange
function onOpen ()
{
var sheet = SpreadsheetApp.getActive();
ScriptApp.newTrigger("myFunctionx")
.forSpreadsheet(sheet)
.onChange()
.create();
}
Looking at the documentation for a change event, you can detect that a row was inserted, but the row number is not returned.
https://developers.google.com/apps-script/guides/triggers/events#change
function myFunctionx(e) {
Logger.log("Change3");
Logger.log(e.changeType);
}
You might be able to find the row that was inserted by looking at the last row in the active range.
function myFunctionx(e) {
Logger.log("Change3");
Logger.log(e.changeType);
Logger.log(SpreadsheetApp.getActiveRange().getLastRow());
}
It is possible for a change to affect more than one row if you copy and paste into multiple cells. You should loop through all of the rows in your range.
function onEdit(e) {
for(var i=e.range.getRow(); i<= e.range.getLastRow(); i++) {
SpreadsheetApp.getActiveSheet().
getRange(i, 10).setValue(e.range.getA1Notation());
}
}
onChange does trigger every time a new row is added(through sheets API).
function zapOnChange(e){
if(e.changeType !== 'INSERT_ROW') return;//exit if the type of change is not a row insertion
const insertedRow = SpreadsheetApp.getActive().getActiveRange();
//do something with inserted row
}
Important notes:
Sheets API runs asynchronously. onChange is triggered randomly and NOT in the order of insertion.
For eg, Two simultaneous inserts may occur at A2:F2 and A3:F3, But the insertRow at A2:F2 may trigger onChange after insertion of A3:F3. So, at the time zapOnChange() is run, the last row might already be A3:F3. So, you cannot use .getLastRow() to determine the inserted row, but use .getActiveRange()
Sometimes multiple rows are inserted: .getActiveRange().getNumRows() provides number of inserted rows.

Running onEdit trigger when sheet is updated by Zapier

I have a Google spreadsheet where I would like to automatically update one field every time a new row is added by a Zapier automation.
Every time I test the script manually by adding a row or making edits on existing rows, the script fires as expected. However, when Zapier adds a new row to the spreadsheet absolutely nothing happens.
Any idea why and how I can remedy this problem?
This the script which I am trying to use:
function onEdit(e) {
var row = e.range.getRow();
SpreadsheetApp.getActiveSheet().
getRange(row, 10).setValue('My_New_Value');
}
I was able to replicate the behavior you are seeing in your script. I believe that when you add a row outside of the active data range, it does not trigger an edit event. In order to catch those events, you will also need to catch chagne events.
https://developers.google.com/apps-script/reference/script/spreadsheet-trigger-builder#onchange
function onOpen ()
{
var sheet = SpreadsheetApp.getActive();
ScriptApp.newTrigger("myFunctionx")
.forSpreadsheet(sheet)
.onChange()
.create();
}
Looking at the documentation for a change event, you can detect that a row was inserted, but the row number is not returned.
https://developers.google.com/apps-script/guides/triggers/events#change
function myFunctionx(e) {
Logger.log("Change3");
Logger.log(e.changeType);
}
You might be able to find the row that was inserted by looking at the last row in the active range.
function myFunctionx(e) {
Logger.log("Change3");
Logger.log(e.changeType);
Logger.log(SpreadsheetApp.getActiveRange().getLastRow());
}
It is possible for a change to affect more than one row if you copy and paste into multiple cells. You should loop through all of the rows in your range.
function onEdit(e) {
for(var i=e.range.getRow(); i<= e.range.getLastRow(); i++) {
SpreadsheetApp.getActiveSheet().
getRange(i, 10).setValue(e.range.getA1Notation());
}
}
onChange does trigger every time a new row is added(through sheets API).
function zapOnChange(e){
if(e.changeType !== 'INSERT_ROW') return;//exit if the type of change is not a row insertion
const insertedRow = SpreadsheetApp.getActive().getActiveRange();
//do something with inserted row
}
Important notes:
Sheets API runs asynchronously. onChange is triggered randomly and NOT in the order of insertion.
For eg, Two simultaneous inserts may occur at A2:F2 and A3:F3, But the insertRow at A2:F2 may trigger onChange after insertion of A3:F3. So, at the time zapOnChange() is run, the last row might already be A3:F3. So, you cannot use .getLastRow() to determine the inserted row, but use .getActiveRange()
Sometimes multiple rows are inserted: .getActiveRange().getNumRows() provides number of inserted rows.

Clock Trigger Builder Not calling function when scheduled - Google sheets app Script

I am using the app script provided by Google to access their prediction API through sheets. I am trying to predict thousands of rows at once, however, after 6 minutes the maximum execution time is reached at the code stops.
I implemented a solution that I found using clock trigger builder. Once I run the function it goes for 5 mins, then it stops sets a trigger to recall the function within 2 mins.
The major problem is that the function is not called when scheduled. I see it in the current triggers list, but it never gets called again. Can you please explain why this is occurring.
My intention is to predict as many lines as possible in 5 min then stop set a trigger to call the predict function again within a few minutes start where it left off and continue until ever element has been predicted.
I also need to know how would I store then values in cache so that it would know all the information that it needs when the function is called again.
//This is the function that is used to predict a selection of data
function predict() {
try {
clearOutput();
var startTime= (new Date()).getTime();
var sheet = SpreadsheetApp.getActiveSheet();
var selection = sheet.getActiveSelection();
var instances = selection.getValues();
var project_number = getProjectNumber();
var model_name = getModelName();
var startRow = stRow;
var MAX_RUNNING_TIME = 300000;
var REASONABLE_TIME_TO_WAIT = 60000;
for (var i = startRow; i < instances.length; ++i) {
var currTime = (new Date()).getTime();
if(currTime - startTime >= MAX_RUNNING_TIME) {
var builder = ScriptApp.newTrigger('predict').timeBased().after(REASONABLE_TIME_TO_WAIT);
builder.create();
break;
} else {
var result = predictSingleRow(project_number, model_name, instances[i]);
selection.getCell(i + 1, 1).setValue(result);
}
}
} catch(e) {
Browser.msgBox('ERROR:' + e, Browser.Buttons.OK);
}
}
Few things as to why your code is not functioning as intended:
1) Since you mentioned,"I see it in the current triggers list, but it never gets called again" and looking at your code, I am unsure whether you intended to call the function again after it's execution has completed. If you do, this is because your for loop runs for a while until the length of the instances is obtained. Nothing in the script suggests that the function needs to be run again once it has finished iterating through instances. Refer to this link to see how to Manage Trigger Programmatically.
2) var builder = ScriptApp.newTrigger('predict').timeBased().after(REASONABLE_TIME_TO_WAIT);
This line of your code falls under an if condition which stops the execution for 1 minute (value is 60000). Hence, adding 1 minute to the time since execution started. Nowhere are you resetting the startTime counter to the time after the waiting time since once the value of currTime - startTime has exceeded MAX_RUNNING_TIME, the function will keep calling the if loop for all iterations of the for loop after that. Simply put, if startTime was 9:35 and currTime was 9:40, after waiting for 1 minute the currTime is 9:41 which is still more than the MAX_RUNNING_TIME(5 minutes) because value of startTime still remains 9:35. Resetting it to 9:41 at this point should resolve your problem.
3) Loosing the break in the if loop would probably help fix that as well.
EDIT:
Add a function as shown in the link I mentioned above:
function callTrigger(){
ScriptApp.newTrigger('predict')
.timeBased()
.everyMinutes(30)
.create();
}
Run the function callTrigger once from your editor and you should be good to go. Remember, for minutes you can only pass values 1,5,15 or 30.