86 lines
		
	
	
		
			2.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			86 lines
		
	
	
		
			2.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| const retry = require('retry');
 | |
| 
 | |
| const networkErrorMsgs = [
 | |
| 	'Failed to fetch', // Chrome
 | |
| 	'NetworkError when attempting to fetch resource.', // Firefox
 | |
| 	'The Internet connection appears to be offline.', // Safari
 | |
| 	'Network request failed' // `cross-fetch`
 | |
| ];
 | |
| 
 | |
| class AbortError extends Error {
 | |
| 	constructor(message) {
 | |
| 		super();
 | |
| 
 | |
| 		if (message instanceof Error) {
 | |
| 			this.originalError = message;
 | |
| 			({message} = message);
 | |
| 		} else {
 | |
| 			this.originalError = new Error(message);
 | |
| 			this.originalError.stack = this.stack;
 | |
| 		}
 | |
| 
 | |
| 		this.name = 'AbortError';
 | |
| 		this.message = message;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| const decorateErrorWithCounts = (error, attemptNumber, options) => {
 | |
| 	// Minus 1 from attemptNumber because the first attempt does not count as a retry
 | |
| 	const retriesLeft = options.retries - (attemptNumber - 1);
 | |
| 
 | |
| 	error.attemptNumber = attemptNumber;
 | |
| 	error.retriesLeft = retriesLeft;
 | |
| 	return error;
 | |
| };
 | |
| 
 | |
| const isNetworkError = errorMessage => networkErrorMsgs.includes(errorMessage);
 | |
| 
 | |
| const pRetry = (input, options) => new Promise((resolve, reject) => {
 | |
| 	options = {
 | |
| 		onFailedAttempt: () => {},
 | |
| 		retries: 10,
 | |
| 		...options
 | |
| 	};
 | |
| 
 | |
| 	const operation = retry.operation(options);
 | |
| 
 | |
| 	operation.attempt(async attemptNumber => {
 | |
| 		try {
 | |
| 			resolve(await input(attemptNumber));
 | |
| 		} catch (error) {
 | |
| 			if (!(error instanceof Error)) {
 | |
| 				reject(new TypeError(`Non-error was thrown: "${error}". You should only throw errors.`));
 | |
| 				return;
 | |
| 			}
 | |
| 
 | |
| 			if (error instanceof AbortError) {
 | |
| 				operation.stop();
 | |
| 				reject(error.originalError);
 | |
| 			} else if (error instanceof TypeError && !isNetworkError(error.message)) {
 | |
| 				operation.stop();
 | |
| 				reject(error);
 | |
| 			} else {
 | |
| 				decorateErrorWithCounts(error, attemptNumber, options);
 | |
| 
 | |
| 				try {
 | |
| 					await options.onFailedAttempt(error);
 | |
| 				} catch (error) {
 | |
| 					reject(error);
 | |
| 					return;
 | |
| 				}
 | |
| 
 | |
| 				if (!operation.retry(error)) {
 | |
| 					reject(operation.mainError());
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	});
 | |
| });
 | |
| 
 | |
| module.exports = pRetry;
 | |
| // TODO: remove this in the next major version
 | |
| module.exports.default = pRetry;
 | |
| 
 | |
| module.exports.AbortError = AbortError;
 |