diff --git a/source/core/index.ts b/source/core/index.ts index d3b635c99..987770e6c 100644 --- a/source/core/index.ts +++ b/source/core/index.ts @@ -15,6 +15,7 @@ import http2wrapper = require('http2-wrapper'); import lowercaseKeys = require('lowercase-keys'); import ResponseLike = require('responselike'); import is, {assert} from '@sindresorhus/is'; +import applyDestroyPatch from './utils/apply-destroy-patch'; import getBodySize from './utils/get-body-size'; import isFormData from './utils/is-form-data'; import proxyEvents from './utils/proxy-events'; @@ -1379,6 +1380,9 @@ export default class Request extends Duplex implements RequestEvents { highWaterMark: 0 }); + // TODO: Remove this when targeting Node.js 14 + applyDestroyPatch(this); + this[kDownloadedSize] = 0; this[kUploadedSize] = 0; this.requestInitialized = false; diff --git a/source/core/utils/apply-destroy-patch.ts b/source/core/utils/apply-destroy-patch.ts new file mode 100644 index 000000000..9b2713505 --- /dev/null +++ b/source/core/utils/apply-destroy-patch.ts @@ -0,0 +1,18 @@ +import {Readable, Writable} from 'stream'; + +export default function applyDestroyPatch(stream: Readable | Writable): void { + const kDestroy = Symbol('destroy'); + + if (Number(process.versions.node.split('.')[0]) >= 14) { + return; + } + + // @ts-expect-error + stream[kDestroy] = stream.destroy; + stream.destroy = (...args) => { + if (!stream.destroyed) { + // @ts-expect-error + return stream[kDestroy](...args); + } + }; +} diff --git a/test/stream.ts b/test/stream.ts index 686b052cc..214882e88 100644 --- a/test/stream.ts +++ b/test/stream.ts @@ -11,6 +11,7 @@ import * as FormData from 'form-data'; import is from '@sindresorhus/is'; import got, {RequestError} from '../source/index'; import withServer from './helpers/with-server'; +import delay = require('delay'); const pStreamPipeline = promisify(stream.pipeline); @@ -419,6 +420,22 @@ test('async iterator works', withServer, async (t, server, got) => { t.is(Buffer.concat(chunks).toString(), payload); }); +test('destroys only once', async t => { + const stream = got.stream('https://example.com'); + stream.destroy(); + stream.destroy(new Error('oh no')); + + let errored = false; + + stream.once('error', () => { + errored = true; + }); + + await delay(1); + + t.false(errored); +}); + if (Number.parseInt(process.versions.node.split('.')[0]!, 10) <= 12) { test('does not emit end event on error', withServer, async (t, server, got) => { server.get('/', infiniteHandler);