Skip to content

Instantly share code, notes, and snippets.

@mayrsascha
Forked from Bajena/RetriableRequestsBatch.gs
Last active February 24, 2022 22:00
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mayrsascha/dd39019378b6688bf304a4cdc5cc55b1 to your computer and use it in GitHub Desktop.
Save mayrsascha/dd39019378b6688bf304a4cdc5cc55b1 to your computer and use it in GitHub Desktop.
Retriable requests batch for Google Apps Scripts. Originally seen on https://medium.com/@sidehacker/nice-did-you-consider-making-this-a-gas-library-dee19ce3db4a. Install it by adding the library script ID "16rm4lelUzHsrF_vLJOwYFh6HvnZHM5LhT8zOn45YvdeQdsZVZmlIOhDP".
/**
* --- UrlFetchApp WITH retries ---
*
* Don't let your script fail easily because of bad API uptime.
*
* Calls provided HTTP requests batch and retries in case of errors. This function has the same
* params and return value as URLFetchApp.fetchAll().
* https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app#fetchurl-params
*
* @param {Array<object>} requests - Array of request param objects
* @param {Object} options - Configuration options object
* @param {number} [options.maxTries=3] - Maximum number of times the batch can be called.
* @param {Array<string>} [options.retriableErrorStrings] - Requests will
* be retried only when the response payload includes this string. If this parameter
* is omitted this condition doesn't apply.
* @throws {UrlFetchAppWithRetriesTooManyRetriesError} The batch exceeded maxTries retries.
* @throws {UrlFetchAppWithRetriesNonRetriableError} The failed request is not a retriable error
*
* @return {Array<HTTPResponse>} responses (in the same order as requests)
*
* @example
*
* const request1 = { 'url': 'https://httpbin.org/get', 'method' : 'get'};
* const request2 = { 'url': 'https://httpbin.org/post', 'method' : 'post'};
*
* UrlFetchAppWithRetries.fetchAll([request1, request2])
*
* @example
*
* const request1 = { 'url': 'https://httpbin.org/get', 'method' : 'get'};
* const request2 = { 'url': 'https://httpbin.org/post', 'method' : 'post'};
*
* UrlFetchAppWithRetries.fetchAll([request1, request2], { maxTries: 2, retriableErrorStrings: ['timed out'] })
*/
function fetchAll(requests, options) {
options = options || {};
var maxTries = options.maxTries || 3;
var retriableErrorStrings = options.retriableErrorStrings;
var requests = _initializeRequestObjects(requests);
return _performRequests(requests, maxTries, retriableErrorStrings);
};
function _initializeRequestObjects(requests) {
var requestObjects = [];
for (var i = 0; i < requests.length; i++) {
// Make sure URLFetchApp doesn't crash on errors
requests[i].muteHttpExceptions = true;
requestObjects.push({ index: i, request: requests[i], response: null });
}
return requestObjects;
};
function UrlFetchAppWithRetriesTooManyRetriesError(message) {
this.message = message;
this.name = 'UrlFetchAppWithRetriesTooManyRetriesError';
this.stack = new Error().stack;
};
function UrlFetchAppWithRetriesNonRetriableError(message) {
this.message = message;
this.name = 'UrlFetchAppWithRetriesNonRetriableError';
this.stack = new Error().stack;
};
function _performRequests(requests, maxTries, retriableErrorStrings) {
var tries = 1;
var toCall = requests;
while (tries <= maxTries && toCall.length) {
if (tries > 1) {
console.log('Retrying requests', tries, toCall);
}
_fetchAndPopulateResponses(toCall);
// If a non-retriable errors happens immediately throw an error
var failedNonRetriable = requests.filter(function(requestObject) {
return _requestDidFail(requestObject) && !_retriableErrorOccured(requestObject, retriableErrorStrings);
});
if (failedNonRetriable.length > 0) {
var nonRetriableError = _getFirstResponseBody(failedNonRetriable);
throw new UrlFetchAppWithRetriesNonRetriableError(nonRetriableError);
}
toCall = requests.filter(function(requestObject) {
return _requestDidFail(requestObject) && _retriableErrorOccured(requestObject, retriableErrorStrings);
});
tries++;
}
// If retriable errors happens too many times return an error
if (toCall.length > 0) {
var tooManyRetriesError = _getFirstResponseBody(toCall);
throw new UrlFetchAppWithRetriesTooManyRetriesError(tooManyRetriesError);
}
return requests.map(function(requestObject) { return requestObject.response; });
};
function _fetchAndPopulateResponses(requestObjects) {
var requestsToCall = requestObjects.map(function(d) { return d.request; });
var responses = UrlFetchApp.fetchAll(requestsToCall);
for (var i = 0; i < responses.length; i++) {
requestObjects[i].response = responses[i];
}
};
function _getFirstResponseBody(requestObjects) {
return requestObjects[0].response.getContentText();
};
function _retriableErrorOccured(requestObject, retriableErrorStrings) {
if (!retriableErrorStrings) { return true; }
var error = requestObject.response.getContentText();
return retriableErrorStrings.filter(function(s) {
return error.indexOf(s) >= 0;
}).length > 0;
};
function _requestDidFail(requestObject) {
return requestObject.response.getResponseCode() >= 400;
};
@jacobwilson41
Copy link

I can't find the library in GAS. Is it still available?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment