The following Google Apps Script is designed to explore Exponential Backoff - a process by which when something fails you try again a set number of times whilst increasing the delay between each attempt, up to a certain point.
I needed this for a tool I built which adds Guests to a Calendar event from a Google Form submission. Whilst I was using ScriptLock to prevent simultaneous submissions, the code ran so fast that it would infrequently trip the Calendar API with the following error message "API call to calendar.events.patch failed with error: Rate Limit Exceeded".
By infrequently I mean a reported issue only once in 3,500 submissions over the course of 12 months. Enough however to take the opportunity to learn about Exponential Backoff and to squash that single instance.
Just a note that this is one way to implement it.
Sample Apps Script code for Exponential Backoff |
The Code
There are a few elements of the code I want to draw out specifically, such as 'count' which is a variable we create to determine how many times we should iterate through our 'while()' loop when retrying the failed process - set to 3 in this example. Then there is our 'backOff' variable which is what we use for 'Utilities.sleep()' to determine how long we want to delay the process by - set to 2 seconds in this example (the actual value it requires needs to be in milliseconds):
// how many times the 'while' loop should iterate throughvar count = 3;// the delay in how long the code should wait before retrying, in millisecondsvar backOff = 2000; // 2 seconds
In order to generate the specific error message I receive when updating Google Calendar events quicker than the API allows, I use the 'Math.random()' JavaScript method to generate a random number that I then test against a particular value. In this example by using '0.5' I create a 50% chance of the error message being shown via 'throw new Error'. You could tweak this to '0.9' for instance to create a 90% chance of seeing the error message if you require for your testing:
if (Math.random() < 0.5) {throw new Error("API call to calendar.events.patch failed with error: Rate Limit Exceeded");};
We wrap the error in a try/catch to then allow us to perform a JavaScript 'match' on it to check it's the actual error message we are expecting. You can read about filtering a try/catch error message here. If we find a match in the error message we need to decrement our 'count' variable by 1 to acknowledge we've gone through the process once. We then need to check the value of this variable to determine if it has reached 0 - in which case we want to 'throw' the error message back to our Parent Function as we have reached the limit on how many times we wanted the while() loop to occur. Or we want our code implement the pause ('backOff') to slow it down, remembering to double the length of this afterwards (for instance 2 seconds to 4 seconds to 8 seconds ... etc):
// decrement count by 1;count -= 1;// if count is 0 then number of iterations has been reachedif (count == 0) {// return error message to 'catch' of Parent Functionthrow (error);} else {// pause code for backoff period before trying againUtilities.sleep(backOff);// double length of backoff period each timebackOff *= 2;};
Download
Exponential Backoff download (please use 'Overview' > 'Make a copy' for your own version).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Code to learn about Exponential Backoff. | |
* Developed when experiencing 'Rate Limit' issue with updating a Calendar Event of Attendees. | |
* | |
* DEVELOPED BY THE GIFT OF SCRIPT: https://www.pbainbridge.co.uk/ | |
*/ | |
function mainFunction() { | |
console.log("Starting the process"); | |
try { | |
// how many times the 'while' loop should iterate through | |
var count = 3; | |
// the delay in how long the code should wait before retrying, in milliseconds | |
var backOff = 2000; // 2 seconds | |
// call Function | |
var returnMessage = testBackOff(count, backOff); | |
// only displayed if previous Function completed successfully without error/throw | |
console.log("returnMessage is: " + returnMessage); | |
} catch (error) { | |
console.log("Catch error is: " + error.stack); | |
}; | |
}; | |
function testBackOff(count, backOff) { | |
// loop through for the number of times specified in Parent Function | |
while (count > 0) { | |
try { | |
console.log("count is: " + count); | |
// create a defined error message with 50% chance (0.5) of triggering when the code is run | |
if (Math.random() < 0.5) { | |
throw new Error("API call to calendar.events.patch failed with error: Rate Limit Exceeded"); | |
}; | |
return "Successfully updated Calendar Attendees."; | |
} catch (error) { | |
// convert 'catch' message to string and perform JavaScript 'match' for keywords | |
var errorString = error.toString(); | |
var matching = errorString.match(/Rate Limit Exceeded/gi); | |
// test if 'match' is true to display specific log message | |
if (matching) { | |
// log message | |
console.log('Message match found'); | |
// decrement count by 1; | |
count -= 1; | |
// if count is 0 then number of iterations has been reached | |
if (count == 0) { | |
// return error message to 'catch' of Parent Function | |
throw (error); | |
} else { | |
// pause code for backoff period before trying again | |
Utilities.sleep(backOff); | |
// double length of backoff period each time | |
backOff *= 2; | |
}; | |
} else { | |
// log message | |
console.log('Different message found.'); | |
throw (error); | |
}; | |
}; | |
}; | |
}; |
No comments:
Post a Comment