-
-
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".
This file contains 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
/** | |
* --- 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; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I can't find the library in GAS. Is it still available?