Retry try catch in Google Apps Script - javascript

When processing results of Google forms with Google Apps Script accessing the form by …
let formID = FormApp.getActiveForm().getId();
… sometimes fails with an exception like "Form data could not be retrieved." Manually started just a minute later it works properly.
To handle those errors the best way I want to catch the exception and retry the method one minute later. I came up with this:
function foo() {
const maxTries = 3;
let formID;
let tries = 1;
while(true) {
try {
formID = FormApp.getActiveForm().getId();
break;
} catch (e) {
console.log("Retrieving form data failed (" + tries + ")");
if (tries >= maxTries) {
console.log("Retrieving form data not possible"); // and/or …
throw(e);
return;
} else {
tries++;
};
};
};
// Do things with form stuff
};
How can I insert a 60 second pause between the tries? And I'm not sure anyway, if there isn't a better way to overcome those errors (at all or within Google Apps Script).

In this case why don't you try using Utilities.sleep. This method will pretty much puts the script to sleep for a set amount of time. Please note the maximum amount is of 5 minutes as the maximum execution time for a script is of 6 minutes. You can check runtime limits in the documentation.

Related

How to check what the throttling limit is for your access to an endpoint with JS

[![enter image description here][1]][1]I need to implement code to check what my throttling limit is on an endpoint (I know it's x times per minute). I've only been able to find an example of this in python, which I have never used. It seems like my options are to run a script to send the request repeatedly until it throttles me or, if possible, query the API to see what the limit is.
Does anyone have a good idea on how to go about this?
Thanks.
Note: The blank space is just data from the api calls.
[1]: https://i.stack.imgur.com/gAFQQ.png
This starts concurency number of workers (I'm using workers as a loose term here; don't # me). Each one makes as many requests as possible until one of the requests is rate-limited or it runs out of time. It them reports how many of the requests completed successfully inside the given time window.
If you know the rate-limit window (1 minute based on your question), this will find the rate-limit. If you need to discover the window, you would want to intentionally exhaust the limit, then slow down the requests and measure the time until they started going through again. The provided code does not do this.
// call apiCall() a bunch of times, stopping when a apiCall() resolves
// false or when "until" time is reached, whichever comes first. For example
// if your limit is 50 req/min (and you give "until" enough time to
// actuially complete 50+ requests) this will call apiCall() 50 times. Each
// call should return a promise resolving to TRUE, so it will be counted as
// a success. On the 51st call you will presumably hit the limit, the API
// will return an error, apiCall() will detect that, and resolve to false.
// This will cause the worker to stop making requests and return 50.
async function workerThread(apiCall, until) {
let successfullRequests = 0;
while(true) {
const success = await apiCall();
// only count it if the request was successfull
// AND finished within the timeframe
if(success && Date.now() < until) {
successfullRequests++;
} else {
break;
}
}
return successfullRequests;
}
// this just runs a bunch of workerThreads in parallell, since by doing a
// single request at a time, you might not be able to hit the limit
// depending on how slow the API is to return. It returns the sum of each
// workerThread(), AKA the total number of apiCall()s that resolved to TRUE
// across all threads.
async function testLimit(apiCall, concurency, time) {
const endTime = Date.now() + time;
// launch "concurency" number of requests
const workers = [];
while(workers.length < concurency) {
workers.push(workerThread(apiCall, endTime));
}
// sum the number of requests that succeded from each worker.
// this implicitly waits for them to finish.
let total = 0;
for(const worker of workers) {
total += await worker;
}
return total;
}
// put in your own code to make a trial API call.
// return true for success or false if you were throttled.
async function yourAPICall() {
try {
// this is a really sloppy example API
// the limit is ROUGHLY 5/min, but because of the sloppy server-side
// implimentation you might get 4-6.
const resp = await fetch("https://9072997.com/demos/rate-limit/");
return resp.ok;
} catch {
return false;
}
}
// this is a demo of how to use the function
(async function() {
// run 2 requests at a time for 5 seconds
const limit = await testLimit(yourAPICall, 2, 5*1000);
console.log("limit is " + limit + " requests in 5 seconds");
})();
Note that this method measures the quota available to itself. If other clients or previous requests have already depleted the quota, it will affect the result.

Why is this LockService not working as expected when sending email?

I'm using LockService when sending emails, but when multiple users run it at the same time, it throws the error: Service invoked too many times...
Here's some log, showing the failures:
function sendEmail() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
var lock = LockService.getScriptLock();
try {
lock.waitLock(3000); // wait 03 seconds for others' use of the code section and lock to stop and then proceed
} catch (e) {
Logger.log('Someone has just sent an Email. Try it again 3 seconds later.');
return HtmlService.createHtmlOutput("<b> Server Busy please try after some time <p>")
// In case this a server side code called asynchronously you return a error code and display the appropriate message on the client side
//return "Error: Server busy try again later... Sorry :("
}
/*
Gets certain data from current row. These are used as criteria
*/
GmailApp.sendEmail(email, "Text", name + " BODY.", { name: 'Diplay Custom Name as Sender' });
//Looks for a matching record in another sheet to mark its adjacent column as sent ("Yes")
for (var n = 0; n < formRespValues.length; n++) {
if (formRespValues[n][1] == testNo) {
formRespSheet.getRange('M' + (2 + n)).setValue('Yes');
}
}
}
SpreadsheetApp.flush(); // applies all pending spreadsheet changes
lock.releaseLock();
}
As #Tanaike pointed out in the comments, a short time within waitLock(timeoutInMillis) or tryLock(timeoutInMillis) may prevent the Lock Service from working as expected.
To ensure the correct functioning, try setting a time that is largely greater than the execution time of your script. If it is less than this, the lock will "open" prematurely, and cause errors such as those outlined in the question.
Documentation :
Lock Service
Class Lock

Code halts unexpectedly on backend of my wix site

I wrote an asynchronous function which is supposed to check if a given url returns a '200' from a get and otherwise wait a few seconds to try again a limited number of times. The code works just fine when I run it in my computer using node but when I transfer it to backend it only checks for the site once and then immediately stops when receiving an error. What am I doing wrong?
async function wait(url,delay=10000,attp=3){
let t0 = new Date(Date.now());
let attempts = 0;
console.log('starting...');
async function check(){
if(attempts<attp){
console.log('ATTEMPTS: ',attempts);
return await request.get(url).on('error',
async function(err){
console.log('ERROR: ',err);
attempts+=1;
return await setTimeout(()=>{check()},delay);
}).on('response',
async function(response){
if(response.statusCode===200){
let t1 = new Date(Date.now());
wixData.insert('pagSeguroTimeStats', { 'time': (t1 - t0) / 1000. });
return '200';
}else{
attempts+=1;
console.log('not 200');
return await setTimeout(()=>{check()},delay);
}
});
}else{
return '404';
}
}
return check();
}
Seems that there is a limit to how much time a backend function can run. From the Wix Code forum, it seems that the limit is 14 seconds, although this doesn't look like an official number from Wix.
The 14 second limit only applies to web functions.
Time
Wix allows web modules called from the frontend, HTTP functions, and router hooks to run for up to 14 seconds. This limitation applies to both free and premium sites. Any of these methods that take longer than 14 seconds receives a 504 response code. Note that after 14 seconds the code might still execute, but the connection to the client is closed so the results do not appear in the frontend. This error message appears in your log:
/backend/.js(w)/ timed out because it exceeded the maximum execution time.
I've got a .js function that just stop, and I get no error or anything.

Cross-window synchronization (critical sections) in the browser

I'm trying to achieve the following in a web page:
Users can open multiple tabs/windows of the page.
Every few seconds, I need exactly one of those tabs/windows to execute a specific section of code (critical region).
I don't care which one of the tabs/windows executes the code, i.e. no need to worry about the fairness or starvation properties of the solution.
Since the user opened the tabs/windows him/herself, the different instances of the page have no knowledge about or direct references to each other (i.e. no window.parent, etc.)
I don't want to require Flash or Silverlight or other plugins and everything needs to run client-side, so the ways in which the tabs/windows can communicate are very limited (LocalStorage is the only one I found so far, but there might be others).
Any of the tabs/windows can crash or be closed or refreshed at any time and more tabs/windows can be opened at any time also, and the remaining windows must "react" such that I still get exactly one execution of the critical region every few seconds.
This needs to run reliably in as many browsers as possible, including mobile (caniuse-rating of over %90).
My first attempt at a solution was to use a simple mutual exclusion algorithm that uses LocalStorage as the shared memory. For various reasons, I chose the mutual exclusion algorithm by Burns and Lynch from their paper "Mutual Exclusion Using Indivisible Reads and Writes" (page 4 (836)).
I built a jsfiddle (see code below) to try the idea out and it works beautifully in Firefox. If you'd like to try it, open the link to the fiddle in several (up to 20) windows of Firefox and watch exactly one of them blink orange every second. If you see more than one blink at the same time, let me know! :) (Note: the way I assign the IDs in the fiddle is a little cheesy (simply looping over 0..19) and things will only work if every window was assigned a different ID. If two windows show the same ID, simply reload one.).
Unfortunately, in Chrome and especially in Internet Explorer things don't work as planned (multiple windows blink). I think this is due to a delay in the propagation of the data I write to LocalStorage from one tab/window to the other (see my question about this here).
So, basically, I need to find either a different mutex algorithm that can handle delayed data (sounds difficult/impossible) or I need to find an entirely different approach. Maybe StorageEvents can help? Or maybe there is a different mechanism that doesn't use LocalStorage?
For completeness, here is the code of the fiddle:
// Global constants
var LOCK_TIMEOUT = 300; // Locks time out after 300ms
var INTERVAL = 1000; // Critical section should run every second
//==================================================================================
// Assign process ID
var myID;
id = window.localStorage.getItem("id");
if (id==null) id = 0;
id = Number(id);
myID = id;
id = (id+1) % 20;
window.localStorage.setItem("id", id);
document.documentElement.innerHTML = "ID: "+myID;
//==================================================================================
// Method to indicate critical section
var lastBlink = 0;
function blink() {
col = Math.round(Math.min((new Date().getTime() - lastBlink)*2/3, 255));
document.body.style.backgroundColor = "rgb(255, "+((col >> 1)+128)+", "+col+")";
}
//==================================================================================
// Helper methods to implement expiring flags
function flagUp() {
window.localStorage.setItem("F"+myID, new Date().getTime());
}
function flagDown() {
window.localStorage.setItem("F"+myID, 0);
}
// Try to refresh flag timeout and return whether we're sure that it never expired
function refreshFlag() {
content = window.localStorage.getItem("F"+myID);
if (content==null) return false;
content = Number(content);
if ((content==NaN) || (Math.abs(new Date().getTime() - content)>=timeout))
return false;
window.localStorage.setItem("F"+myID, new Date().getTime());
return Math.abs(new Date().getTime() - content) < timeout;
}
function setFlag(key) {
window.localStorage.setItem(key, new Date().getTime());
}
function checkFlag(key, timeout) {
content = window.localStorage.getItem(key);
if (content==null) return false;
content = Number(content);
if (content==NaN) return false;
return Math.abs(new Date().getTime() - content) < timeout;
}
//==================================================================================
// Burns-Lynch mutual exclusion algorithm
var atLine7 = false;
function enterCriticalRegion() {
// Refresh flag timeout and restart algorithm if flag may have expired
if (atLine7) atLine7 &= refreshFlag();
// Check if run is due
if (checkFlag("LastRun", INTERVAL)) return false;
if (!atLine7) {
// 3: F[i] down
flagDown();
// 4: for j:=1 to i-1 do if F[j] = up goto 3
for (j=0; j<myID; j++)
if (checkFlag("F"+j, LOCK_TIMEOUT)) return false;
// 5: F[i] up
flagUp();
// 6: for j:=1 to i-1 do if F[j] = up goto 3
for (j=0; j<myID; j++)
if (checkFlag("F"+j, LOCK_TIMEOUT)) return false;
atLine7 = true;
}
// 7: for j:=i+1 to N do if F[j] = up goto 7
for (j=myID+1; j<20; j++)
if (checkFlag("F"+j, LOCK_TIMEOUT)) return false;
// Check again if run is due
return !checkFlag("LastRun", INTERVAL);
}
function leaveCriticalRegion() {
// Remember time of last succesful run
setFlag("LastRun");
// Release lock on critical region
atLine7 = false;
window.localStorage.setItem("F"+myID, 0);
}
//==================================================================================
// Keep trying to enter critical region and blink on success
function run() {
if (enterCriticalRegion()) {
lastBlink = new Date().getTime();
leaveCriticalRegion();
}
}
// Go!
window.setInterval(run, 10);
window.setInterval(blink, 10);

Debugging a Windows store app - error messages always show the same line number

I've set up error logging on my windows store app, which reports the error message and the line number in the code.
I've been getting two error messages from the same line, line 301. The error messages are
The process cannot access the file because it is being used by another process.
Access is denied.
Based on the First error message I presume the error is with my autosave function, but without the line number I can't say where it's failing. Here's my autosave code
function autosave()
{
if ((localSettings.values["useAutoSave"] == null || localSettings.values["useAutoSave"] == "true")) {
editorContent = editor.textContent;
var text_length = editorContent.length;
if (editedSinceSave && text_length > 0) {
localSettings.values["lastContent"] = text_length;
setStatus("<span class='loader'></span>autosaving", 2000);
writeTempFile();
}
}
window.setTimeout(autosave, _autosave_timeout);
}
function writeTempFile()
{
try{
tempFolder.createFileAsync("tempFile.txt", Windows.Storage.CreationCollisionOption.replaceExisting)
.then(function (theFile) {
return Windows.Storage.FileIO.writeTextAsync(theFile, editor.textContent);
}).done(function () {
localSettings.values["lastPos"] = _lastStartPos;
});
}
catch (e) {
// statements to handle any exceptions
logError(e); // pass exception object to error handler
}
}
Even we I move all my functions around and recompile my code, the error is always at line 301. I suspect that the errorline I'm seeing is actually from whatever underlying js files are used to run my app, but I don't know where to access them. How the hell do I debug this?
Make sure there's not a capability that you have not declared. It doesn't look like your code is using anything special, but it's worth a try.

Categories

Resources