This repository has been archived by the owner on Feb 12, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 18
/
index.js
119 lines (105 loc) · 2.95 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
const retry = require('async-retry');
const debug = require('debug')('fetch-retry');
// retry settings
const MIN_TIMEOUT = 10;
const MAX_RETRIES = 5;
const MAX_RETRY_AFTER = 20;
const FACTOR = 6;
module.exports = exports = setup;
function isClientError(err) {
if (!err) return false;
return (
err.code === 'ERR_UNESCAPED_CHARACTERS' ||
err.message === 'Request path contains unescaped characters'
);
}
function setup(fetch) {
if (!fetch) {
fetch = require('node-fetch');
}
async function fetchRetry(url, opts = {}) {
const retryOpts = Object.assign(
{
// timeouts will be [10, 60, 360, 2160, 12960]
// (before randomization is added)
minTimeout: MIN_TIMEOUT,
retries: MAX_RETRIES,
factor: FACTOR,
maxRetryAfter: MAX_RETRY_AFTER,
},
opts.retry
);
if (opts.onRetry) {
retryOpts.onRetry = (error) => {
opts.onRetry(error, opts);
if (opts.retry && opts.retry.onRetry) {
opts.retry.onRetry(error);
}
};
}
try {
return await retry(async (bail, attempt) => {
const { method = 'GET' } = opts;
try {
// this will be retried
const res = await fetch(url, opts);
debug('status %d', res.status);
if ((res.status >= 500 && res.status < 600) || res.status === 429) {
// NOTE: doesn't support http-date format
const retryAfter = parseInt(res.headers.get('retry-after'), 10);
if (retryAfter) {
if (retryAfter > retryOpts.maxRetryAfter) {
return res;
} else {
await new Promise((r) => setTimeout(r, retryAfter * 1e3));
}
}
throw new ResponseError(res);
} else {
return res;
}
} catch (err) {
if (err.type === 'aborted') {
return bail(err);
}
const clientError = isClientError(err);
const isRetry = !clientError && attempt <= retryOpts.retries;
debug(
`${method} ${url} error (status = ${err.status}). ${
isRetry ? 'retrying' : ''
}`,
err
);
if (clientError) {
return bail(err);
}
throw err;
}
}, retryOpts);
} catch (err) {
if (err instanceof ResponseError) {
return err.res;
}
throw err;
}
}
for (const key of Object.keys(fetch)) {
fetchRetry[key] = fetch[key];
}
fetchRetry.default = fetchRetry;
return fetchRetry;
}
class ResponseError extends Error {
constructor(res) {
super(res.statusText);
if (Error.captureStackTrace) {
Error.captureStackTrace(this, ResponseError);
}
this.name = this.constructor.name;
this.res = res;
// backward compat
this.code = this.status = this.statusCode = res.status;
this.url = res.url;
}
}
exports.ResponseError = ResponseError;