undici 4.15.1 → 5.1.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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Node CI](https://github.com/nodejs/undici/actions/workflows/nodejs.yml/badge.svg)](https://github.com/nodejs/undici/actions/workflows/nodejs.yml) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/) [![npm version](https://badge.fury.io/js/undici.svg)](https://badge.fury.io/js/undici) [![codecov](https://codecov.io/gh/nodejs/undici/branch/main/graph/badge.svg?token=yZL6LtXkOA)](https://codecov.io/gh/nodejs/undici)
4
4
 
5
- A HTTP/1.1 client, written from scratch for Node.js.
5
+ An HTTP/1.1 client, written from scratch for Node.js.
6
6
 
7
7
  > Undici means eleven in Italian. 1.1 -> 11 -> Eleven -> Undici.
8
8
  It is also a Stranger Things reference.
@@ -65,7 +65,15 @@ for await (const data of body) {
65
65
  console.log('trailers', trailers)
66
66
  ```
67
67
 
68
- Using [the body mixin from the Fetch Standard](https://fetch.spec.whatwg.org/#body-mixin).
68
+ ## Body Mixins
69
+
70
+ The `body` mixins are the most common way to format the request/response body. Mixins include:
71
+
72
+ - [`.formData()`](https://fetch.spec.whatwg.org/#dom-body-formdata)
73
+ - [`.json()`](https://fetch.spec.whatwg.org/#dom-body-json)
74
+ - [`.text()`](https://fetch.spec.whatwg.org/#dom-body-text)
75
+
76
+ Example usage:
69
77
 
70
78
  ```js
71
79
  import { request } from 'undici'
@@ -83,6 +91,12 @@ console.log('data', await body.json())
83
91
  console.log('trailers', trailers)
84
92
  ```
85
93
 
94
+ _Note: Once a mixin has been called then the body cannot be reused, thus calling additional mixins on `.body`, e.g. `.body.json(); .body.text()` will result in an error `TypeError: unusable` being thrown and returned through the `Promise` rejection._
95
+
96
+ Should you need to access the `body` in plain-text after using a mixin, the best practice is to use the `.text()` mixin first and then manually parse the text to the desired format.
97
+
98
+ For more information about their behavior, please reference the body mixin from the [Fetch Standard](https://fetch.spec.whatwg.org/#body-mixin).
99
+
86
100
  ## Common API Methods
87
101
 
88
102
  This section documents our most commonly used API methods. Additional APIs are documented in their own files within the [docs](./docs/) folder and are accessible via the navigation list on the left side of the docs site.
@@ -213,7 +227,7 @@ const data = {
213
227
 
214
228
  #### `response.body`
215
229
 
216
- Nodejs has two kinds of streams: [web streams](https://nodejs.org/dist/latest-v16.x/docs/api/webstreams.html) which follow the API of the WHATWG web standard found in browsers, and an older Node-specific [streams API](https://nodejs.org/api/stream.html). `response.body` returns a readable web stream. If you would prefer to work with a Node stream you can convert a web stream using `.fromWeb()`.
230
+ Nodejs has two kinds of streams: [web streams](https://nodejs.org/dist/latest-v16.x/docs/api/webstreams.html), which follow the API of the WHATWG web standard found in browsers, and an older Node-specific [streams API](https://nodejs.org/api/stream.html). `response.body` returns a readable web stream. If you would prefer to work with a Node stream you can convert a web stream using `.fromWeb()`.
217
231
 
218
232
  ```js
219
233
  import {fetch} from 'undici';
@@ -228,7 +242,7 @@ Nodejs has two kinds of streams: [web streams](https://nodejs.org/dist/latest-v1
228
242
 
229
243
  #### Specification Compliance
230
244
 
231
- This section documents parts of the [Fetch Standard](https://fetch.spec.whatwg.org) which Undici does
245
+ This section documents parts of the [Fetch Standard](https://fetch.spec.whatwg.org) that Undici does
232
246
  not support or does not fully implement.
233
247
 
234
248
  ##### Garbage Collection
@@ -239,7 +253,7 @@ The [Fetch Standard](https://fetch.spec.whatwg.org) allows users to skip consumi
239
253
  [garbage collection](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management#garbage_collection) to release connection resources. Undici does not do the same. Therefore, it is important to always either consume or cancel the response body.
240
254
 
241
255
  Garbage collection in Node is less aggressive and deterministic
242
- (due to the lack of clear idle periods that browser have through the rendering refresh rate)
256
+ (due to the lack of clear idle periods that browsers have through the rendering refresh rate)
243
257
  which means that leaving the release of connection resources to the garbage collector can lead
244
258
  to excessive connection usage, reduced performance (due to less connection re-use), and even
245
259
  stalls or deadlocks when running out of connections.
@@ -301,7 +315,7 @@ Returns: `Dispatcher`
301
315
 
302
316
  ## Specification Compliance
303
317
 
304
- This section documents parts of the HTTP/1.1 specification which Undici does
318
+ This section documents parts of the HTTP/1.1 specification that Undici does
305
319
  not support or does not fully implement.
306
320
 
307
321
  ### Expect
@@ -334,7 +348,7 @@ aborted.
334
348
 
335
349
  ### Manual Redirect
336
350
 
337
- Since it is not possible to manually follow an HTTP redirect on server-side,
351
+ Since it is not possible to manually follow an HTTP redirect on the server-side,
338
352
  Undici returns the actual response instead of an `opaqueredirect` filtered one
339
353
  when invoked with a `manual` redirect. This aligns `fetch()` with the other
340
354
  implementations in Deno and Cloudflare Workers.
@@ -193,7 +193,7 @@ Returns: `Boolean` - `false` if dispatcher is busy and further dispatch calls wo
193
193
  * **path** `string`
194
194
  * **method** `string`
195
195
  * **body** `string | Buffer | Uint8Array | stream.Readable | Iterable | AsyncIterable | null` (optional) - Default: `null`
196
- * **headers** `UndiciHeaders` (optional) - Default: `null`
196
+ * **headers** `UndiciHeaders | string[]` (optional) - Default: `null`.
197
197
  * **idempotent** `boolean` (optional) - Default: `true` if `method` is `'HEAD'` or `'GET'` - Whether the requests can be safely retried or not. If `false` the request won't be sent until all preceding requests in the pipeline has completed.
198
198
  * **blocking** `boolean` (optional) - Default: `false` - Whether the response is expected to take a long time and would end up blocking the pipeline. When this is set to `true` further pipelining will be avoided on the same connection until headers have been received.
199
199
  * **upgrade** `string | null` (optional) - Default: `null` - Upgrade the request. Should be used to specify the kind of upgrade i.e. `'Websocket'`.
@@ -489,6 +489,8 @@ The `RequestOptions.method` property should not be value `'CONNECT'`.
489
489
  - `body`
490
490
  - `bodyUsed`
491
491
 
492
+ `body` can not be consumed twice. For example, calling `text()` after `json()` throws `TypeError`.
493
+
492
494
  `body` contains the following additional extensions:
493
495
 
494
496
  - `dump({ limit: Integer })`, dump the response by reading up to `limit` bytes without killing the socket (optional) - Default: 262144.
@@ -72,11 +72,7 @@ const mockAgent = new MockAgent()
72
72
  setGlobalDispatcher(mockAgent)
73
73
 
74
74
  const mockPool = mockAgent.get('http://localhost:3000')
75
-
76
- mockPool.intercept({
77
- path: '/foo',
78
- method: 'GET'
79
- }).reply(200, 'foo')
75
+ mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
80
76
 
81
77
  const { statusCode, body } = await request('http://localhost:3000/foo')
82
78
 
@@ -95,11 +91,7 @@ import { MockAgent, request } from 'undici'
95
91
  const mockAgent = new MockAgent()
96
92
 
97
93
  const mockPool = mockAgent.get('http://localhost:3000')
98
-
99
- mockPool.intercept({
100
- path: '/foo',
101
- method: 'GET'
102
- }).reply(200, 'foo')
94
+ mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
103
95
 
104
96
  const {
105
97
  statusCode,
@@ -121,11 +113,7 @@ import { MockAgent, request } from 'undici'
121
113
  const mockAgent = new MockAgent()
122
114
 
123
115
  const mockPool = mockAgent.get('http://localhost:3000')
124
-
125
- mockPool.intercept({
126
- path: '/foo',
127
- method: 'GET'
128
- }).reply(200, 'foo')
116
+ mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
129
117
 
130
118
  const {
131
119
  statusCode,
@@ -147,11 +135,7 @@ import { MockAgent, request } from 'undici'
147
135
  const mockAgent = new MockAgent({ connections: 1 })
148
136
 
149
137
  const mockClient = mockAgent.get('http://localhost:3000')
150
-
151
- mockClient.intercept({
152
- path: '/foo',
153
- method: 'GET'
154
- }).reply(200, 'foo')
138
+ mockClient.intercept({ path: '/foo' }).reply(200, 'foo')
155
139
 
156
140
  const {
157
141
  statusCode,
@@ -174,16 +158,8 @@ const mockAgent = new MockAgent()
174
158
  setGlobalDispatcher(mockAgent)
175
159
 
176
160
  const mockPool = mockAgent.get('http://localhost:3000')
177
-
178
- mockPool.intercept({
179
- path: '/foo',
180
- method: 'GET'
181
- }).reply(200, 'foo')
182
-
183
- mockPool.intercept({
184
- path: '/hello',
185
- method: 'GET'
186
- }).reply(200, 'hello')
161
+ mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
162
+ mockPool.intercept({ path: '/hello'}).reply(200, 'hello')
187
163
 
188
164
  const result1 = await request('http://localhost:3000/foo')
189
165
 
@@ -250,11 +226,7 @@ const mockAgent = new MockAgent()
250
226
  setGlobalDispatcher(mockAgent)
251
227
 
252
228
  const mockPool = mockAgent.get(new RegExp('http://localhost:3000'))
253
-
254
- mockPool.intercept({
255
- path: '/foo',
256
- method: 'GET',
257
- }).reply(200, 'foo')
229
+ mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
258
230
 
259
231
  const {
260
232
  statusCode,
@@ -277,11 +249,7 @@ const mockAgent = new MockAgent()
277
249
  setGlobalDispatcher(mockAgent)
278
250
 
279
251
  const mockPool = mockAgent.get((origin) => origin === 'http://localhost:3000')
280
-
281
- mockPool.intercept({
282
- path: '/foo',
283
- method: 'GET'
284
- }).reply(200, 'foo')
252
+ mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
285
253
 
286
254
  const {
287
255
  statusCode,
@@ -328,11 +296,7 @@ import { MockAgent } from 'undici'
328
296
  const mockAgent = new MockAgent()
329
297
 
330
298
  const mockPool = mockAgent.get('http://localhost:3000')
331
-
332
- mockPool.intercept({
333
- path: '/foo',
334
- method: 'GET'
335
- }).reply(200, 'foo')
299
+ mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
336
300
 
337
301
  const {
338
302
  statusCode,
@@ -481,3 +445,79 @@ mockAgent.disableNetConnect()
481
445
  await request('http://example.com')
482
446
  // Will throw
483
447
  ```
448
+
449
+ ### `MockAgent.pendingInterceptors()`
450
+
451
+ This method returns any pending interceptors registered on a mock agent. A pending interceptor meets one of the following criteria:
452
+
453
+ - Is registered with neither `.times(<number>)` nor `.persist()`, and has not been invoked;
454
+ - Is persistent (i.e., registered with `.persist()`) and has not been invoked;
455
+ - Is registered with `.times(<number>)` and has not been invoked `<number>` of times.
456
+
457
+ Returns: `PendingInterceptor[]` (where `PendingInterceptor` is a `MockDispatch` with an additional `origin: string`)
458
+
459
+ #### Example - List all pending inteceptors
460
+
461
+ ```js
462
+ const agent = new MockAgent()
463
+ agent.disableNetConnect()
464
+
465
+ agent
466
+ .get('https://example.com')
467
+ .intercept({ method: 'GET', path: '/' })
468
+ .reply(200, '')
469
+
470
+ const pendingInterceptors = agent.pendingInterceptors()
471
+ // Returns [
472
+ // {
473
+ // timesInvoked: 0,
474
+ // times: 1,
475
+ // persist: false,
476
+ // consumed: false,
477
+ // pending: true,
478
+ // path: '/',
479
+ // method: 'GET',
480
+ // body: undefined,
481
+ // headers: undefined,
482
+ // data: {
483
+ // error: null,
484
+ // statusCode: 200,
485
+ // data: '',
486
+ // headers: {},
487
+ // trailers: {}
488
+ // },
489
+ // origin: 'https://example.com'
490
+ // }
491
+ // ]
492
+ ```
493
+
494
+ ### `MockAgent.assertNoPendingInterceptors([options])`
495
+
496
+ This method throws if the mock agent has any pending interceptors. A pending interceptor meets one of the following criteria:
497
+
498
+ - Is registered with neither `.times(<number>)` nor `.persist()`, and has not been invoked;
499
+ - Is persistent (i.e., registered with `.persist()`) and has not been invoked;
500
+ - Is registered with `.times(<number>)` and has not been invoked `<number>` of times.
501
+
502
+ #### Example - Check that there are no pending interceptors
503
+
504
+ ```js
505
+ const agent = new MockAgent()
506
+ agent.disableNetConnect()
507
+
508
+ agent
509
+ .get('https://example.com')
510
+ .intercept({ method: 'GET', path: '/' })
511
+ .reply(200, '')
512
+
513
+ agent.assertNoPendingInterceptors()
514
+ // Throws an UndiciError with the following message:
515
+ //
516
+ // 1 interceptor is pending:
517
+ //
518
+ // ┌─────────┬────────┬───────────────────────┬──────┬─────────────┬────────────┬─────────────┬───────────┐
519
+ // │ (index) │ Method │ Origin │ Path │ Status code │ Persistent │ Invocations │ Remaining │
520
+ // ├─────────┼────────┼───────────────────────┼──────┼─────────────┼────────────┼─────────────┼───────────┤
521
+ // │ 0 │ 'GET' │ 'https://example.com' │ '/' │ 200 │ '❌' │ 0 │ 1 │
522
+ // └─────────┴────────┴───────────────────────┴──────┴─────────────┴────────────┴─────────────┴───────────┘
523
+ ```
@@ -58,10 +58,7 @@ import { MockAgent } from 'undici'
58
58
  const mockAgent = new MockAgent({ connections: 1 })
59
59
 
60
60
  const mockClient = mockAgent.get('http://localhost:3000')
61
- mockClient.intercept({
62
- path: '/foo',
63
- method: 'GET',
64
- }).reply(200, 'foo')
61
+ mockClient.intercept({ path: '/foo' }).reply(200, 'foo')
65
62
 
66
63
  const {
67
64
  statusCode,
@@ -95,11 +95,7 @@ setGlobalDispatcher(mockAgent)
95
95
 
96
96
  // MockPool
97
97
  const mockPool = mockAgent.get('http://localhost:3000')
98
-
99
- mockPool.intercept({
100
- path: '/foo',
101
- method: 'GET',
102
- }).reply(200, 'foo')
98
+ mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
103
99
 
104
100
  const {
105
101
  statusCode,
@@ -5,17 +5,20 @@ Undici have its own mocking [utility](../api/MockAgent.md). It allow us to inter
5
5
  Example:
6
6
 
7
7
  ```js
8
- // index.mjs
8
+ // bank.mjs
9
9
  import { request } from 'undici'
10
10
 
11
- export async function bankTransfer(recepient, ammount) {
12
- const { body } = await request('http://localhost:3000/bank-transfer',
11
+ export async function bankTransfer(recepient, amount) {
12
+ const { body } = await request('http://localhost:3000/bank-transfer',
13
13
  {
14
14
  method: 'POST',
15
15
  headers: {
16
16
  'X-TOKEN-SECRET': 'SuperSecretToken',
17
17
  },
18
- body: JSON.stringify({ recepient })
18
+ body: JSON.stringify({
19
+ recepient,
20
+ amount
21
+ })
19
22
  }
20
23
  )
21
24
  return await body.json()
@@ -28,7 +31,7 @@ And this is what the test file looks like:
28
31
  // index.test.mjs
29
32
  import { strict as assert } from 'assert'
30
33
  import { MockAgent, setGlobalDispatcher, } from 'undici'
31
- import { bankTransfer } from './undici.mjs'
34
+ import { bankTransfer } from './bank.mjs'
32
35
 
33
36
  const mockAgent = new MockAgent();
34
37
 
@@ -46,7 +49,7 @@ mockPool.intercept({
46
49
  },
47
50
  body: JSON.stringify({
48
51
  recepient: '1234567890',
49
- ammount: '100'
52
+ amount: '100'
50
53
  })
51
54
  }).reply(200, {
52
55
  message: 'transaction processed'
@@ -94,7 +97,7 @@ mockPool.intercept({
94
97
 
95
98
  const badRequest = await bankTransfer('1234567890', '100')
96
99
  // Will throw an error
97
- // MockNotMatchedError: Mock dispatch not matched for path '/bank-transfer':
100
+ // MockNotMatchedError: Mock dispatch not matched for path '/bank-transfer':
98
101
  // subsequent request to origin http://localhost:3000 was not allowed (net.connect disabled)
99
102
  ```
100
103
 
package/index.d.ts CHANGED
@@ -16,6 +16,7 @@ import { request, pipeline, stream, connect, upgrade } from './types/api'
16
16
  export * from './types/fetch'
17
17
  export * from './types/file'
18
18
  export * from './types/formdata'
19
+ export { Interceptable } from './types/mock-interceptor'
19
20
 
20
21
  export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, MockClient, MockPool, MockAgent, mockErrors, ProxyAgent }
21
22
  export default Undici
package/lib/agent.js CHANGED
@@ -1,20 +1,14 @@
1
1
  'use strict'
2
2
 
3
- const {
4
- ClientClosedError,
5
- InvalidArgumentError,
6
- ClientDestroyedError
7
- } = require('./core/errors')
8
- const { kClients, kRunning } = require('./core/symbols')
9
- const Dispatcher = require('./dispatcher')
3
+ const { InvalidArgumentError } = require('./core/errors')
4
+ const { kClients, kRunning, kClose, kDestroy, kDispatch } = require('./core/symbols')
5
+ const DispatcherBase = require('./dispatcher-base')
10
6
  const Pool = require('./pool')
11
7
  const Client = require('./client')
12
8
  const util = require('./core/util')
13
9
  const RedirectHandler = require('./handler/redirect')
14
10
  const { WeakRef, FinalizationRegistry } = require('./compat/dispatcher-weakref')()
15
11
 
16
- const kDestroyed = Symbol('destroyed')
17
- const kClosed = Symbol('closed')
18
12
  const kOnConnect = Symbol('onConnect')
19
13
  const kOnDisconnect = Symbol('onDisconnect')
20
14
  const kOnConnectionError = Symbol('onConnectionError')
@@ -30,7 +24,7 @@ function defaultFactory (origin, opts) {
30
24
  : new Pool(origin, opts)
31
25
  }
32
26
 
33
- class Agent extends Dispatcher {
27
+ class Agent extends DispatcherBase {
34
28
  constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) {
35
29
  super()
36
30
 
@@ -60,8 +54,6 @@ class Agent extends Dispatcher {
60
54
  this[kClients].delete(key)
61
55
  }
62
56
  })
63
- this[kClosed] = false
64
- this[kDestroyed] = false
65
57
 
66
58
  const agent = this
67
59
 
@@ -94,76 +86,38 @@ class Agent extends Dispatcher {
94
86
  return ret
95
87
  }
96
88
 
97
- dispatch (opts, handler) {
98
- if (!handler || typeof handler !== 'object') {
99
- throw new InvalidArgumentError('handler must be an object.')
89
+ [kDispatch] (opts, handler) {
90
+ let key
91
+ if (opts.origin && (typeof opts.origin === 'string' || opts.origin instanceof URL)) {
92
+ key = String(opts.origin)
93
+ } else {
94
+ throw new InvalidArgumentError('opts.origin must be a non-empty string or URL.')
100
95
  }
101
96
 
102
- try {
103
- if (!opts || typeof opts !== 'object') {
104
- throw new InvalidArgumentError('opts must be an object.')
105
- }
106
-
107
- let key
108
- if (opts.origin && (typeof opts.origin === 'string' || opts.origin instanceof URL)) {
109
- key = String(opts.origin)
110
- } else {
111
- throw new InvalidArgumentError('opts.origin must be a non-empty string or URL.')
112
- }
113
-
114
- if (this[kDestroyed]) {
115
- throw new ClientDestroyedError()
116
- }
117
-
118
- if (this[kClosed]) {
119
- throw new ClientClosedError()
120
- }
97
+ const ref = this[kClients].get(key)
121
98
 
122
- const ref = this[kClients].get(key)
123
-
124
- let dispatcher = ref ? ref.deref() : null
125
- if (!dispatcher) {
126
- dispatcher = this[kFactory](opts.origin, this[kOptions])
127
- .on('drain', this[kOnDrain])
128
- .on('connect', this[kOnConnect])
129
- .on('disconnect', this[kOnDisconnect])
130
- .on('connectionError', this[kOnConnectionError])
131
-
132
- this[kClients].set(key, new WeakRef(dispatcher))
133
- this[kFinalizer].register(dispatcher, key)
134
- }
135
-
136
- const { maxRedirections = this[kMaxRedirections] } = opts
137
- if (maxRedirections != null && maxRedirections !== 0) {
138
- opts = { ...opts, maxRedirections: 0 } // Stop sub dispatcher from also redirecting.
139
- handler = new RedirectHandler(this, maxRedirections, opts, handler)
140
- }
141
-
142
- return dispatcher.dispatch(opts, handler)
143
- } catch (err) {
144
- if (typeof handler.onError !== 'function') {
145
- throw new InvalidArgumentError('invalid onError method')
146
- }
99
+ let dispatcher = ref ? ref.deref() : null
100
+ if (!dispatcher) {
101
+ dispatcher = this[kFactory](opts.origin, this[kOptions])
102
+ .on('drain', this[kOnDrain])
103
+ .on('connect', this[kOnConnect])
104
+ .on('disconnect', this[kOnDisconnect])
105
+ .on('connectionError', this[kOnConnectionError])
147
106
 
148
- handler.onError(err)
107
+ this[kClients].set(key, new WeakRef(dispatcher))
108
+ this[kFinalizer].register(dispatcher, key)
149
109
  }
150
- }
151
-
152
- get closed () {
153
- return this[kClosed]
154
- }
155
-
156
- get destroyed () {
157
- return this[kDestroyed]
158
- }
159
110
 
160
- close (callback) {
161
- if (callback != null && typeof callback !== 'function') {
162
- throw new InvalidArgumentError('callback must be a function')
111
+ const { maxRedirections = this[kMaxRedirections] } = opts
112
+ if (maxRedirections != null && maxRedirections !== 0) {
113
+ opts = { ...opts, maxRedirections: 0 } // Stop sub dispatcher from also redirecting.
114
+ handler = new RedirectHandler(this, maxRedirections, opts, handler)
163
115
  }
164
116
 
165
- this[kClosed] = true
117
+ return dispatcher.dispatch(opts, handler)
118
+ }
166
119
 
120
+ async [kClose] () {
167
121
  const closePromises = []
168
122
  for (const ref of this[kClients].values()) {
169
123
  const client = ref.deref()
@@ -173,27 +127,10 @@ class Agent extends Dispatcher {
173
127
  }
174
128
  }
175
129
 
176
- if (!callback) {
177
- return Promise.all(closePromises)
178
- }
179
-
180
- // Should never error.
181
- Promise.all(closePromises).then(() => process.nextTick(callback))
130
+ await Promise.all(closePromises)
182
131
  }
183
132
 
184
- destroy (err, callback) {
185
- if (typeof err === 'function') {
186
- callback = err
187
- err = null
188
- }
189
-
190
- if (callback != null && typeof callback !== 'function') {
191
- throw new InvalidArgumentError('callback must be a function')
192
- }
193
-
194
- this[kClosed] = true
195
- this[kDestroyed] = true
196
-
133
+ async [kDestroy] (err) {
197
134
  const destroyPromises = []
198
135
  for (const ref of this[kClients].values()) {
199
136
  const client = ref.deref()
@@ -203,12 +140,7 @@ class Agent extends Dispatcher {
203
140
  }
204
141
  }
205
142
 
206
- if (!callback) {
207
- return Promise.all(destroyPromises)
208
- }
209
-
210
- // Should never error.
211
- Promise.all(destroyPromises).then(() => process.nextTick(callback))
143
+ await Promise.all(destroyPromises)
212
144
  }
213
145
  }
214
146
 
@@ -88,14 +88,16 @@ class RequestHandler extends AsyncResource {
88
88
  this.res = body
89
89
  const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
90
90
 
91
- this.runInAsyncScope(callback, null, null, {
92
- statusCode,
93
- headers,
94
- trailers: this.trailers,
95
- opaque,
96
- body,
97
- context
98
- })
91
+ if (callback !== null) {
92
+ this.runInAsyncScope(callback, null, null, {
93
+ statusCode,
94
+ headers,
95
+ trailers: this.trailers,
96
+ opaque,
97
+ body,
98
+ context
99
+ })
100
+ }
99
101
  }
100
102
 
101
103
  onData (chunk) {
@@ -20,7 +20,7 @@ const kFactory = Symbol('factory')
20
20
  const kOptions = Symbol('options')
21
21
 
22
22
  function defaultFactory (origin, opts) {
23
- return new Pool(origin, opts);
23
+ return new Pool(origin, opts)
24
24
  }
25
25
 
26
26
  class BalancedPool extends PoolBase {