3 Simple Techniques to Insulate Your AdWords Scripts and Keep Them Running Smoothly
No matter how good your scripts, they're still vulnerable when other things go wrong. Columnist Russell Savage explains how to safeguard your work.
The world can be a scary place. You carefully write your amazing AdWords Script code and, like a baby bird learning to fly, you send it out into the world and hope for the best.
But your code makes calls to other APIs that can fail or disappear without warning. It also relies on a spreadsheet that can break or have issues at any time.
You can’t control these external events, but you can create safeguards to isolate yourself from them. And when something bad does happen, you can be notified quickly.
One of the neat things about JavaScript (and many other languages) is the ability to assign functions to variables. That allows you to easily pass functions to other functions as needed. Here is a simple example:
function main() { function theExecutor(theFunctionToExecute) { return theFunctionToExecute(); } function test1() { Logger.log('This is test function 1'); } function test2() { Logger.log('This is test function 2'); } theExecutor(test1); theExecutor(test2); }
The code above illustrates that you can send functions (test1,test2) into other functions (theExecutor) as variables and execute them simply by adding parentheses. We are going to use this technique to create wrappers for our main logic, which will help isolate us from external events.
Finding Slow Code
Every second your is code executing is one more second that something external to your script is able to cause a problem. In order to speed something up, you need to measure it. This is where code profiling comes in.
Profiling is the act of measuring the performance of various parts of your code to identify where the most resources are being used. In the case of AdWords Scripts, we are only really worried about time, so we can create a simple profiler to measure the execution time of our code.
/** * Simple code profiler to measure time * @param funcToProfile - the function to execute (required) * @param storeIn - a hash to store the results in (optional) * If not passed in, logs results to console * @returns {*} value returned by funcToProfile * @author Russ Savage @russellsavage */ function profiler(funcToProfile,storeIn) { if(!funcToProfile) { return; } var start,end,diff,retVal; start = new Date().getTime(); retVal = funcToProfile(); end = new Date().getTime(); diff = end - start; if(storeIn != null) { if(!storeIn[funcToProfile.name]) { storeIn[funcToProfile.name] = diff; } else { storeIn[funcToProfile.name] += diff; } } else { Logger.log(['Ran function ',funcToProfile.name,' in ',diff,'ms.'].join('')); } return retVal; }
The profiler function can be placed around any part of your code you would like to measure in order to determine the execution time. Here is an example:
function main() { profiler(function testing_profiler() { Utilities.sleep(5000); }); }
The code above should print something like “Ran function testing_profiler in 5008ms.” to the logs. If you want to aggregate the total amount of time a particular action inside a loop is executing, you can send in an optional hash argument and then print that value afterwards. Here is an example:
function main() { var profilerLog = {}; // This is where I store the execution time for(var i = 0; i < 10; i++) { profiler(function testing_profiler_loop() { Utilities.sleep(1000); },profilerLog); // Make sure to pass it to the function } printProfilerLog(profilerLog); } /** * A simple function to print the profile log */ function printProfilerLog(log) { Logger.log(''); Logger.log('PROFILER RESULTS'); Logger.log('------------------------'); for(var key in log) { Logger.log(['function ',key,' totaled ',log[key],'ms.'].join('')); } Logger.log('------------------------'); }
Notice how I’m passing the profilerLog into the profiler function on each call. That allows me to aggregate the calls to each named function and print the results using printProfilerLog. The results would look something like this:
PROFILER RESULTS ------------------------ function testing_profiler_loop totaled 10016ms. ------------------------
This should help you figure out where the slow parts of your code are so that you can optimize. Just be sure to remove any profiling code before you go live so that it doesn’t slow things down.
API Retries
Another place this comes in handy is helping with your retry logic for calling external APIs. Sometimes things fail, and most of the time, the best course of action is to simply wait a second and try the call again. Using a similar technique as above, we can implement a generic retry function that can be used with any external call.
/** * Simple code to retry a given function * @param theFuncToRetry - the function to retry (required) * @param retryTimes - the number of times to retry (optional) * @returns {*} value returned by theFuncToRetry * @author Russ Savage @russellsavage */ function retry(theFuncToRetry,retryTimes) { var times = retryTimes || 3; while(times > 0) { try { return theFuncToRetry(); } catch(e) { Logger.log(e); times--; if(times > 0) { Logger.log('Retrying '+times+' more times...'); Utilities.sleep(1000); } else { Logger.log('Out of retries...'); throw e; } } } }
Using this, you can cleanly retry your API calls a few times if they fail without cluttering up your code with a bunch of “while” loops.
Notice the call to Utilities.sleep() function in the retry logic. It is usually a good practice to wait some amount of time before retrying the call to give the server a chance to recover. An example for using this code would be as follows:
function main() { var resp = retry(function() { return UrlFetchApp.fetch('https://www.example.com/api.json'); }/*,10*/); //uncomment this to retry 10 times instead of 3 }
You could also send in the number of times you wanted to retry, otherwise, the default is three. Now you don’t have to worry about intermittent server issues with your APIs.
Notify Yourself When Things Go Wrong
The final application that I find extremely useful is to notify you when your script starts throwing exceptions.
Many times, the external API changes or even AdWords Scripts changes and your calls will start to fail. Wrapping your entire main function allows you to easily be notified whenever your script starts failing.
/** * Sends an email when there is an error in theFunction * @param theFunction - the function to execute (required) * @returns {*} value returned by theFunction * @author Russ Savage @russellsavage */ function notifyOnError(theFunction) { var TO_NOTIFY = ['[email protected]']; try { return theFunction(); } catch(e) { Logger.log(e); var subject = '[SCRIPT FAILURE] '+theFunction.name; var body = 'The script '+theFunction.name+' has failed with the following error: '+e; for(var i in TO_NOTIFY) { MailApp.sendEmail(TO_NOTIFY[i],subject,body); } } }
This simple function can be placed at the beginning of your main() function to automatically alert you via email if your code starts failing. Now, not only will you see the message in the logs, but you will receive an email with the error message as well.
function main() { notifyOnError(function theNameOfTheScript() { var resp = retry(function() { return UrlFetchApp.fetch('https://www.example.com/api.json'); }); }); }
Conclusion
The ability to pass functions into other functions as arguments can be very helpful in keeping your scripts isolated from the danger of the outside world.
They can help you profile your code to find the slow spots and also make it easier to read and maintain by removing the retry and error reporting logic from your main functions.
But these are only a few examples. How are you using this technique in your AdWords scripts?
Contributing authors are invited to create content for Search Engine Land and are chosen for their expertise and contribution to the search community. Our contributors work under the oversight of the editorial staff and contributions are checked for quality and relevance to our readers. The opinions they express are their own.
Related stories
New on Search Engine Land