got 9.3.0 → 9.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "got",
3
- "version": "9.3.0",
3
+ "version": "9.5.0",
4
4
  "description": "Simplified HTTP requests",
5
5
  "license": "MIT",
6
6
  "repository": "sindresorhus/got",
@@ -34,7 +34,7 @@
34
34
  "electron"
35
35
  ],
36
36
  "dependencies": {
37
- "@sindresorhus/is": "^0.12.0",
37
+ "@sindresorhus/is": "^0.14.0",
38
38
  "@szmarczak/http-timer": "^1.1.0",
39
39
  "cacheable-request": "^5.1.0",
40
40
  "decompress-response": "^3.3.0",
@@ -47,17 +47,17 @@
47
47
  "url-parse-lax": "^3.0.0"
48
48
  },
49
49
  "devDependencies": {
50
- "ava": "1.0.0-rc.1",
50
+ "ava": "^1.0.1",
51
51
  "coveralls": "^3.0.0",
52
52
  "delay": "^4.1.0",
53
53
  "form-data": "^2.3.3",
54
54
  "get-port": "^4.0.0",
55
- "np": "^3.0.4",
55
+ "np": "^3.1.0",
56
56
  "nyc": "^13.1.0",
57
57
  "p-event": "^2.1.0",
58
58
  "pem": "^1.13.2",
59
59
  "proxyquire": "^2.0.1",
60
- "sinon": "^7.1.0",
60
+ "sinon": "^7.2.2",
61
61
  "slow-stream": "0.0.4",
62
62
  "tempfile": "^2.0.0",
63
63
  "tempy": "^0.2.1",
package/readme.md CHANGED
@@ -42,8 +42,9 @@ Got is for Node.js. For browsers, we recommend [Ky](https://github.com/sindresor
42
42
  - [Used by ~2000 packages and ~500K repos](https://github.com/sindresorhus/got/network/dependents)
43
43
  - Actively maintained
44
44
 
45
- [See how Got compares to other HTTP libraries](#comparison)
45
+ [Moving from Request?](migration-guides.md)
46
46
 
47
+ [See how Got compares to other HTTP libraries](#comparison)
47
48
 
48
49
  ## Install
49
50
 
@@ -258,22 +259,26 @@ Default:
258
259
  - methods: `GET` `PUT` `HEAD` `DELETE` `OPTIONS` `TRACE`
259
260
  - statusCodes: [`408`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) [`413`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/413) [`429`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) [`500`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) [`502`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502) [`503`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/503) [`504`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504)
260
261
  - maxRetryAfter: `undefined`
262
+ - errorCodes: `ETIMEDOUT` `ECONNRESET` `EADDRINUSE` `ECONNREFUSED` `EPIPE` `ENOTFOUND` `ENETUNREACH` `EAI_AGAIN`
261
263
 
262
- An object representing `retries`, `methods`, `statusCodes` and `maxRetryAfter` fields for the time until retry, allowed methods, allowed status codes and maximum [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) time.
264
+ An object representing `retries`, `methods`, `statusCodes`, `maxRetryAfter` and `errorCodes` fields for the time until retry, allowed methods, allowed status codes, maximum [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) time and allowed error codes.
263
265
 
264
266
  If `maxRetryAfter` is set to `undefined`, it will use `options.timeout`.<br>
265
267
  If [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) header is greater than `maxRetryAfter`, it will cancel the request.
266
268
 
267
- Delays between retries counts with function `1000 * Math.pow(2, retry) + Math.random() * 100`, where `retry` is attempt number (starts from 0).
269
+ Delays between retries counts with function `1000 * Math.pow(2, retry) + Math.random() * 100`, where `retry` is attempt number (starts from 1).
268
270
 
269
271
  The `retries` property can be a `number` or a `function` with `retry` and `error` arguments. The function must return a delay in milliseconds (`0` return value cancels retry).
270
272
 
271
- **Note:** It retries only on the specified methods, status codes, and on these network errors:
273
+ **Note:** By default, it retries *only* on the specified methods, status codes, and on these network errors:
272
274
  - `ETIMEDOUT`: One of the [timeout](#timeout) limits were reached.
273
275
  - `ECONNRESET`: Connection was forcibly closed by a peer.
274
276
  - `EADDRINUSE`: Could not bind to any free port.
275
277
  - `ECONNREFUSED`: Connection was refused by the server.
276
278
  - `EPIPE`: The remote side of the stream being written has been closed.
279
+ - `ENOTFOUND`: Couldn't resolve the hostname to an IP address.
280
+ - `ENETUNREACH`: No internet connection.
281
+ - `EAI_AGAIN`: DNS lookup timed out.
277
282
 
278
283
  ###### followRedirect
279
284
 
@@ -448,6 +453,14 @@ const instance = got.extend({
448
453
 
449
454
  The response object will typically be a [Node.js HTTP response stream](https://nodejs.org/api/http.html#http_class_http_incomingmessage), however, if returned from the cache it will be a [response-like object](https://github.com/lukechilds/responselike) which behaves in the same way.
450
455
 
456
+ ##### request
457
+
458
+ Type: `Object`
459
+
460
+ **Note:** This is not a [http.ClientRequest](https://nodejs.org/api/http.html#http_class_http_clientrequest).
461
+
462
+ - `gotOptions` - The options that were set on this request.
463
+
451
464
  ##### body
452
465
 
453
466
  Type: `string` `Object` *(depending on `options.json`)*
@@ -584,7 +597,7 @@ Sets `options.method` to the method name and makes a request.
584
597
 
585
598
  #### got.extend([options])
586
599
 
587
- Configure a new `got` instance with default `options`. `options` are merged with the parent instance's `defaults.options` using [`got.mergeOptions`](#gotmergeoptionsparentoptions-newoptions).
600
+ Configure a new `got` instance with default `options`. The `options` are merged with the parent instance's `defaults.options` using [`got.mergeOptions`](#gotmergeoptionsparentoptions-newoptions). You can access the resolved options with the `.defaults` property on the instance.
588
601
 
589
602
  ```js
590
603
  const client = got.extend({
@@ -650,9 +663,15 @@ Options are deeply merged to a new object. The value of each key is determined a
650
663
  - If the new property is an `Array`, it overwrites the old one with a deep clone of the new property.
651
664
  - Otherwise, the new value is assigned to the key.
652
665
 
666
+ #### got.defaults
667
+
668
+ Type: `Object`
669
+
670
+ The default Got options.
671
+
653
672
  ## Errors
654
673
 
655
- Each error contains (if available) `body`, `statusCode`, `statusMessage`, `host`, `hostname`, `method`, `path`, `protocol` and `url` properties to make debugging easier.
674
+ Each error contains `host`, `hostname`, `method`, `path`, `protocol`, `url` and `gotOptions` properties to make debugging easier.
656
675
 
657
676
  In Promise mode, the `response` is attached to the error.
658
677
 
@@ -670,15 +689,15 @@ When reading from response stream fails.
670
689
 
671
690
  #### got.ParseError
672
691
 
673
- When `json` option is enabled, server response code is 2xx, and `JSON.parse` fails.
692
+ When `json` option is enabled, server response code is 2xx, and `JSON.parse` fails. Includes `statusCode` and `statusMessage` properties.
674
693
 
675
694
  #### got.HTTPError
676
695
 
677
- When the server response code is not 2xx. Includes `statusCode`, `statusMessage`, and `redirectUrls` properties.
696
+ When the server response code is not 2xx. Includes `body`, `statusCode`, `statusMessage`, and `redirectUrls` properties.
678
697
 
679
698
  #### got.MaxRedirectsError
680
699
 
681
- When the server redirects you more than ten times. Includes a `redirectUrls` property, which is an array of the URLs Got was redirected to before giving up.
700
+ When the server redirects you more than ten times. Includes a `statusCode`, `statusMessage`, and `redirectUrls` property which is an array of the URLs Got was redirected to before giving up.
682
701
 
683
702
  #### got.UnsupportedProtocolError
684
703
 
@@ -690,7 +709,7 @@ When the request is aborted with `.cancel()`.
690
709
 
691
710
  #### got.TimeoutError
692
711
 
693
- When the request is aborted due to a [timeout](#timeout)
712
+ When the request is aborted due to a [timeout](#timeout). Includes an `event` property.
694
713
 
695
714
  ## Aborting the request
696
715
 
@@ -1023,7 +1042,7 @@ const h2got = got.extend({request});
1023
1042
  | Electron support | ✔ | ✖ | ✖ | ✖ |
1024
1043
  | Promise API | ✔ | ✔ | ✔ | ✔ |
1025
1044
  | Stream API | ✔ | ✔ | Node.js only | ✖ |
1026
- | Request cancelation | ✔ | ✖ | | ✔ |
1045
+ | Request cancelation | ✔ | ✖ | | ✔ |
1027
1046
  | RFC compliant caching | ✔ | ✖ | ✖ | ✖ |
1028
1047
  | Cookies (out-of-box) | ✔ | ✔ | ✖ | ✖ |
1029
1048
  | Follows redirects | ✔ | ✔ | ✔ | ✔ |
package/source/errors.js CHANGED
@@ -5,7 +5,7 @@ const PCancelable = require('p-cancelable');
5
5
  const is = require('@sindresorhus/is');
6
6
 
7
7
  class GotError extends Error {
8
- constructor(message, error, opts) {
8
+ constructor(message, error, options) {
9
9
  super(message);
10
10
  Error.captureStackTrace(this, this.constructor);
11
11
  this.name = 'GotError';
@@ -15,13 +15,14 @@ class GotError extends Error {
15
15
  }
16
16
 
17
17
  Object.assign(this, {
18
- host: opts.host,
19
- hostname: opts.hostname,
20
- method: opts.method,
21
- path: opts.path,
22
- socketPath: opts.socketPath,
23
- protocol: opts.protocol,
24
- url: opts.href
18
+ host: options.host,
19
+ hostname: options.hostname,
20
+ method: options.method,
21
+ path: options.path,
22
+ socketPath: options.socketPath,
23
+ protocol: options.protocol,
24
+ url: options.href,
25
+ gotOptions: options
25
26
  });
26
27
  }
27
28
  }
@@ -29,29 +30,29 @@ class GotError extends Error {
29
30
  module.exports.GotError = GotError;
30
31
 
31
32
  module.exports.CacheError = class extends GotError {
32
- constructor(error, opts) {
33
- super(error.message, error, opts);
33
+ constructor(error, options) {
34
+ super(error.message, error, options);
34
35
  this.name = 'CacheError';
35
36
  }
36
37
  };
37
38
 
38
39
  module.exports.RequestError = class extends GotError {
39
- constructor(error, opts) {
40
- super(error.message, error, opts);
40
+ constructor(error, options) {
41
+ super(error.message, error, options);
41
42
  this.name = 'RequestError';
42
43
  }
43
44
  };
44
45
 
45
46
  module.exports.ReadError = class extends GotError {
46
- constructor(error, opts) {
47
- super(error.message, error, opts);
47
+ constructor(error, options) {
48
+ super(error.message, error, options);
48
49
  this.name = 'ReadError';
49
50
  }
50
51
  };
51
52
 
52
53
  module.exports.ParseError = class extends GotError {
53
- constructor(error, statusCode, opts, data) {
54
- super(`${error.message} in "${urlLib.format(opts)}": \n${data.slice(0, 77)}...`, error, opts);
54
+ constructor(error, statusCode, options, data) {
55
+ super(`${error.message} in "${urlLib.format(options)}": \n${data.slice(0, 77)}...`, error, options);
55
56
  this.name = 'ParseError';
56
57
  this.statusCode = statusCode;
57
58
  this.statusMessage = http.STATUS_CODES[this.statusCode];
@@ -59,7 +60,7 @@ module.exports.ParseError = class extends GotError {
59
60
  };
60
61
 
61
62
  module.exports.HTTPError = class extends GotError {
62
- constructor(response, opts) {
63
+ constructor(response, options) {
63
64
  const {statusCode} = response;
64
65
  let {statusMessage} = response;
65
66
 
@@ -68,7 +69,7 @@ module.exports.HTTPError = class extends GotError {
68
69
  } else {
69
70
  statusMessage = http.STATUS_CODES[statusCode];
70
71
  }
71
- super(`Response code ${statusCode} (${statusMessage})`, {}, opts);
72
+ super(`Response code ${statusCode} (${statusMessage})`, {}, options);
72
73
  this.name = 'HTTPError';
73
74
  this.statusCode = statusCode;
74
75
  this.statusMessage = statusMessage;
@@ -78,8 +79,8 @@ module.exports.HTTPError = class extends GotError {
78
79
  };
79
80
 
80
81
  module.exports.MaxRedirectsError = class extends GotError {
81
- constructor(statusCode, redirectUrls, opts) {
82
- super('Redirected 10 times. Aborting.', {}, opts);
82
+ constructor(statusCode, redirectUrls, options) {
83
+ super('Redirected 10 times. Aborting.', {}, options);
83
84
  this.name = 'MaxRedirectsError';
84
85
  this.statusCode = statusCode;
85
86
  this.statusMessage = http.STATUS_CODES[this.statusCode];
@@ -88,15 +89,15 @@ module.exports.MaxRedirectsError = class extends GotError {
88
89
  };
89
90
 
90
91
  module.exports.UnsupportedProtocolError = class extends GotError {
91
- constructor(opts) {
92
- super(`Unsupported protocol "${opts.protocol}"`, {}, opts);
92
+ constructor(options) {
93
+ super(`Unsupported protocol "${options.protocol}"`, {}, options);
93
94
  this.name = 'UnsupportedProtocolError';
94
95
  }
95
96
  };
96
97
 
97
98
  module.exports.TimeoutError = class extends GotError {
98
- constructor(error, opts) {
99
- super(error.message, {code: 'ETIMEDOUT'}, opts);
99
+ constructor(error, options) {
100
+ super(error.message, {code: 'ETIMEDOUT'}, options);
100
101
  this.name = 'TimeoutError';
101
102
  this.event = error.event;
102
103
  }
package/source/index.js CHANGED
@@ -22,6 +22,16 @@ const defaults = {
22
22
  502,
23
23
  503,
24
24
  504
25
+ ],
26
+ errorCodes: [
27
+ 'ETIMEDOUT',
28
+ 'ECONNRESET',
29
+ 'EADDRINUSE',
30
+ 'ECONNREFUSED',
31
+ 'EPIPE',
32
+ 'ENOTFOUND',
33
+ 'ENETUNREACH',
34
+ 'EAI_AGAIN'
25
35
  ]
26
36
  },
27
37
  headers: {
@@ -4,7 +4,6 @@ const urlLib = require('url');
4
4
  const is = require('@sindresorhus/is');
5
5
  const urlParseLax = require('url-parse-lax');
6
6
  const lowercaseKeys = require('lowercase-keys');
7
- const isRetryOnNetworkErrorAllowed = require('./utils/is-retry-on-network-error-allowed');
8
7
  const urlToOptions = require('./utils/url-to-options');
9
8
  const isFormData = require('./utils/is-form-data');
10
9
  const merge = require('./merge');
@@ -56,7 +55,8 @@ const preNormalize = (options, defaults) => {
56
55
  options.retry = {
57
56
  retries: 0,
58
57
  methods: [],
59
- statusCodes: []
58
+ statusCodes: [],
59
+ errorCodes: []
60
60
  };
61
61
 
62
62
  if (is.nonEmptyObject(defaults) && retry !== false) {
@@ -83,20 +83,24 @@ const preNormalize = (options, defaults) => {
83
83
  options.retry.statusCodes = new Set(options.retry.statusCodes);
84
84
  }
85
85
 
86
+ if (is.array(options.retry.errorCodes)) {
87
+ options.retry.errorCodes = new Set(options.retry.errorCodes);
88
+ }
89
+
86
90
  return options;
87
91
  };
88
92
 
89
93
  const normalize = (url, options, defaults) => {
90
- if (is.plainObject(url) && Reflect.has(url, 'url')) {
91
- options = url;
92
- url = url.url;
94
+ if (is.plainObject(url)) {
95
+ options = {...url, ...options};
96
+ url = options.url || {};
93
97
  delete options.url;
94
98
  }
95
99
 
96
100
  if (defaults) {
97
101
  options = merge({}, defaults.options, options ? preNormalize(options, defaults.options) : {});
98
102
  } else {
99
- options = merge({}, options ? preNormalize(options) : {});
103
+ options = merge({}, preNormalize(options));
100
104
  }
101
105
 
102
106
  if (!is.string(url) && !is.object(url)) {
@@ -112,22 +116,14 @@ const normalize = (url, options, defaults) => {
112
116
  url = urlToOptions(new URL(url, options.baseUrl));
113
117
  } else {
114
118
  url = url.replace(/^unix:/, 'http://$&');
115
-
116
119
  url = urlParseLax(url);
117
- if (url.auth) {
118
- throw new Error('Basic authentication must be done with the `auth` option');
119
- }
120
120
  }
121
121
  } else if (is(url) === 'URL') {
122
122
  url = urlToOptions(url);
123
123
  }
124
124
 
125
- options = {
126
- path: '',
127
- ...url,
128
- protocol: url.protocol || 'https:', // Override both null/undefined with default protocol
129
- ...options
130
- };
125
+ // Override both null/undefined with default protocol
126
+ options = merge({path: ''}, url, {protocol: url.protocol || 'https:'}, options);
131
127
 
132
128
  const {baseUrl} = options;
133
129
  Object.defineProperty(options, 'baseUrl', {
@@ -214,33 +210,31 @@ const normalize = (url, options, defaults) => {
214
210
  return 0;
215
211
  }
216
212
 
217
- if (error !== null) {
218
- if (!isRetryOnNetworkErrorAllowed(error) && (!options.retry.methods.has(error.method) || !options.retry.statusCodes.has(error.statusCode))) {
219
- return 0;
220
- }
221
-
222
- if (Reflect.has(error, 'headers') && Reflect.has(error.headers, 'retry-after') && retryAfterStatusCodes.has(error.statusCode)) {
223
- let after = Number(error.headers['retry-after']);
224
- if (is.nan(after)) {
225
- after = Date.parse(error.headers['retry-after']) - Date.now();
226
- } else {
227
- after *= 1000;
228
- }
229
-
230
- if (after > options.retry.maxRetryAfter) {
231
- return 0;
232
- }
213
+ if ((!error || !options.retry.errorCodes.has(error.code)) && (!options.retry.methods.has(error.method) || !options.retry.statusCodes.has(error.statusCode))) {
214
+ return 0;
215
+ }
233
216
 
234
- return after;
217
+ if (Reflect.has(error, 'headers') && Reflect.has(error.headers, 'retry-after') && retryAfterStatusCodes.has(error.statusCode)) {
218
+ let after = Number(error.headers['retry-after']);
219
+ if (is.nan(after)) {
220
+ after = Date.parse(error.headers['retry-after']) - Date.now();
221
+ } else {
222
+ after *= 1000;
235
223
  }
236
224
 
237
- if (error.statusCode === 413) {
225
+ if (after > options.retry.maxRetryAfter) {
238
226
  return 0;
239
227
  }
228
+
229
+ return after;
230
+ }
231
+
232
+ if (error.statusCode === 413) {
233
+ return 0;
240
234
  }
241
235
 
242
236
  const noise = Math.random() * 100;
243
- return ((1 << iteration) * 1000) + noise;
237
+ return ((2 ** (iteration - 1)) * 1000) + noise;
244
238
  };
245
239
  }
246
240
 
@@ -27,7 +27,6 @@ module.exports = (options, input) => {
27
27
  let redirectString;
28
28
  let uploadBodySize;
29
29
  let retryCount = 0;
30
- let retryTries = 0;
31
30
  let shouldAbort = false;
32
31
 
33
32
  const setCookie = options.cookieJar ? util.promisify(options.cookieJar.setCookie.bind(options.cookieJar)) : null;
@@ -93,6 +92,9 @@ module.exports = (options, input) => {
93
92
  response.retryCount = retryCount;
94
93
  response.timings = timings;
95
94
  response.redirectUrls = redirects;
95
+ response.request = {
96
+ gotOptions: options
97
+ };
96
98
 
97
99
  const rawCookies = response.headers['set-cookie'];
98
100
  if (options.cookieJar && rawCookies) {
@@ -120,19 +122,19 @@ module.exports = (options, input) => {
120
122
 
121
123
  redirects.push(redirectString);
122
124
 
123
- const redirectOpts = {
125
+ const redirectOptions = {
124
126
  ...options,
125
127
  ...urlToOptions(redirectURL)
126
128
  };
127
129
 
128
130
  for (const hook of options.hooks.beforeRedirect) {
129
131
  // eslint-disable-next-line no-await-in-loop
130
- await hook(redirectOpts);
132
+ await hook(redirectOptions);
131
133
  }
132
134
 
133
- emitter.emit('redirect', response, redirectOpts);
135
+ emitter.emit('redirect', response, redirectOptions);
134
136
 
135
- await get(redirectOpts);
137
+ await get(redirectOptions);
136
138
  return;
137
139
  }
138
140
  }
@@ -202,9 +204,9 @@ module.exports = (options, input) => {
202
204
 
203
205
  if (options.cache) {
204
206
  const cacheableRequest = new CacheableRequest(fn.request, options.cache);
205
- const cacheReq = cacheableRequest(options, handleResponse);
207
+ const cacheRequest = cacheableRequest(options, handleResponse);
206
208
 
207
- cacheReq.once('error', error => {
209
+ cacheRequest.once('error', error => {
208
210
  if (error instanceof CacheableRequest.RequestError) {
209
211
  emitter.emit('error', new RequestError(error, options));
210
212
  } else {
@@ -212,7 +214,7 @@ module.exports = (options, input) => {
212
214
  }
213
215
  });
214
216
 
215
- cacheReq.once('request', handleRequest);
217
+ cacheRequest.once('request', handleRequest);
216
218
  } else {
217
219
  // Catches errors thrown by calling fn.request(...)
218
220
  try {
@@ -227,7 +229,7 @@ module.exports = (options, input) => {
227
229
  let backoff;
228
230
 
229
231
  try {
230
- backoff = options.retry.retries(++retryTries, error);
232
+ backoff = options.retry.retries(++retryCount, error);
231
233
  } catch (error2) {
232
234
  emitter.emit('error', error2);
233
235
  return;
@@ -241,7 +243,6 @@ module.exports = (options, input) => {
241
243
  await hook(options, error, retryCount);
242
244
  }
243
245
 
244
- retryCount++;
245
246
  await get(options);
246
247
  } catch (error) {
247
248
  emitter.emit('error', error);
@@ -276,7 +277,7 @@ module.exports = (options, input) => {
276
277
  }
277
278
 
278
279
  if (is.undefined(options.headers['content-length']) && is.undefined(options.headers['transfer-encoding'])) {
279
- if (uploadBodySize > 0 || options.method === 'PUT') {
280
+ if ((uploadBodySize > 0 || options.method === 'PUT') && !is.null(uploadBodySize)) {
280
281
  options.headers['content-length'] = uploadBodySize;
281
282
  }
282
283
  }
@@ -12,31 +12,7 @@ class TimeoutError extends Error {
12
12
 
13
13
  const reentry = Symbol('reentry');
14
14
 
15
- function addTimeout(delay, callback, ...args) {
16
- // Event loop order is timers, poll, immediates.
17
- // The timed event may emit during the current tick poll phase, so
18
- // defer calling the handler until the poll phase completes.
19
- let immediate;
20
- const timeout = setTimeout(() => {
21
- immediate = setImmediate(callback, delay, ...args);
22
- /* istanbul ignore next: added in node v9.7.0 */
23
- if (immediate.unref) {
24
- immediate.unref();
25
- }
26
- }, delay);
27
-
28
- /* istanbul ignore next: in order to support electron renderer */
29
- if (timeout.unref) {
30
- timeout.unref();
31
- }
32
-
33
- const cancel = () => {
34
- clearTimeout(timeout);
35
- clearImmediate(immediate);
36
- };
37
-
38
- return cancel;
39
- }
15
+ const noop = () => {};
40
16
 
41
17
  module.exports = (request, delays, options) => {
42
18
  /* istanbul ignore next: this makes sure timed-out isn't called twice */
@@ -45,6 +21,43 @@ module.exports = (request, delays, options) => {
45
21
  }
46
22
 
47
23
  request[reentry] = true;
24
+
25
+ let stopNewTimeouts = false;
26
+
27
+ const addTimeout = (delay, callback, ...args) => {
28
+ // An error had been thrown before. Going further would result in uncaught errors.
29
+ // See https://github.com/sindresorhus/got/issues/631#issuecomment-435675051
30
+ if (stopNewTimeouts) {
31
+ return noop;
32
+ }
33
+
34
+ // Event loop order is timers, poll, immediates.
35
+ // The timed event may emit during the current tick poll phase, so
36
+ // defer calling the handler until the poll phase completes.
37
+ let immediate;
38
+ const timeout = setTimeout(() => {
39
+ immediate = setImmediate(callback, delay, ...args);
40
+ /* istanbul ignore next: added in node v9.7.0 */
41
+ if (immediate.unref) {
42
+ immediate.unref();
43
+ }
44
+ }, delay);
45
+
46
+ /* istanbul ignore next: in order to support electron renderer */
47
+ if (timeout.unref) {
48
+ timeout.unref();
49
+ }
50
+
51
+ const cancel = () => {
52
+ clearTimeout(timeout);
53
+ clearImmediate(immediate);
54
+ };
55
+
56
+ cancelers.push(cancel);
57
+
58
+ return cancel;
59
+ };
60
+
48
61
  const {host, hostname} = options;
49
62
  const timeoutHandler = (delay, event) => {
50
63
  request.emit('error', new TimeoutError(delay, event));
@@ -55,6 +68,7 @@ module.exports = (request, delays, options) => {
55
68
 
56
69
  const cancelers = [];
57
70
  const cancelTimeouts = () => {
71
+ stopNewTimeouts = true;
58
72
  cancelers.forEach(cancelTimeout => cancelTimeout());
59
73
  };
60
74
 
@@ -64,14 +78,15 @@ module.exports = (request, delays, options) => {
64
78
  });
65
79
 
66
80
  if (delays.request !== undefined) {
67
- const cancelTimeout = addTimeout(delays.request, timeoutHandler, 'request');
68
- cancelers.push(cancelTimeout);
81
+ addTimeout(delays.request, timeoutHandler, 'request');
69
82
  }
70
83
 
71
84
  if (delays.socket !== undefined) {
72
85
  request.setTimeout(delays.socket, () => {
73
86
  timeoutHandler(delays.socket, 'socket');
74
87
  });
88
+
89
+ cancelers.push(() => request.setTimeout(0));
75
90
  }
76
91
 
77
92
  if (delays.lookup !== undefined && !request.socketPath && !net.isIP(hostname || host)) {
@@ -79,7 +94,6 @@ module.exports = (request, delays, options) => {
79
94
  /* istanbul ignore next: hard to test */
80
95
  if (socket.connecting) {
81
96
  const cancelTimeout = addTimeout(delays.lookup, timeoutHandler, 'lookup');
82
- cancelers.push(cancelTimeout);
83
97
  socket.once('lookup', cancelTimeout);
84
98
  }
85
99
  });
@@ -89,17 +103,15 @@ module.exports = (request, delays, options) => {
89
103
  request.once('socket', socket => {
90
104
  /* istanbul ignore next: hard to test */
91
105
  if (socket.connecting) {
92
- const timeConnect = () => {
93
- const cancelTimeout = addTimeout(delays.connect, timeoutHandler, 'connect');
94
- cancelers.push(cancelTimeout);
95
- return cancelTimeout;
96
- };
106
+ const timeConnect = () => addTimeout(delays.connect, timeoutHandler, 'connect');
97
107
 
98
108
  if (request.socketPath || net.isIP(hostname || host)) {
99
109
  socket.once('connect', timeConnect());
100
110
  } else {
101
- socket.once('lookup', () => {
102
- socket.once('connect', timeConnect());
111
+ socket.once('lookup', error => {
112
+ if (error === null) {
113
+ socket.once('connect', timeConnect());
114
+ }
103
115
  });
104
116
  }
105
117
  }
@@ -112,7 +124,6 @@ module.exports = (request, delays, options) => {
112
124
  if (socket.connecting) {
113
125
  socket.once('connect', () => {
114
126
  const cancelTimeout = addTimeout(delays.secureConnect, timeoutHandler, 'secureConnect');
115
- cancelers.push(cancelTimeout);
116
127
  socket.once('secureConnect', cancelTimeout);
117
128
  });
118
129
  }
@@ -121,11 +132,7 @@ module.exports = (request, delays, options) => {
121
132
 
122
133
  if (delays.send !== undefined) {
123
134
  request.once('socket', socket => {
124
- const timeRequest = () => {
125
- const cancelTimeout = addTimeout(delays.send, timeoutHandler, 'send');
126
- cancelers.push(cancelTimeout);
127
- return cancelTimeout;
128
- };
135
+ const timeRequest = () => addTimeout(delays.send, timeoutHandler, 'send');
129
136
  /* istanbul ignore next: hard to test */
130
137
  if (socket.connecting) {
131
138
  socket.once('connect', () => {
@@ -140,7 +147,6 @@ module.exports = (request, delays, options) => {
140
147
  if (delays.response !== undefined) {
141
148
  request.once('upload-complete', () => {
142
149
  const cancelTimeout = addTimeout(delays.response, timeoutHandler, 'response');
143
- cancelers.push(cancelTimeout);
144
150
  request.once('response', cancelTimeout);
145
151
  });
146
152
  }
@@ -1,20 +0,0 @@
1
- 'use strict';
2
-
3
- const WHITELIST = new Set([
4
- 'ETIMEDOUT',
5
- 'ECONNRESET',
6
- 'EADDRINUSE',
7
- 'ECONNREFUSED',
8
- 'EPIPE',
9
- 'ENOTFOUND',
10
- 'ENETUNREACH',
11
- 'EAI_AGAIN'
12
- ]);
13
-
14
- module.exports = error => {
15
- if (error && WHITELIST.has(error.code)) {
16
- return true;
17
- }
18
-
19
- return false;
20
- };