From ce796adeeb010274577fcc88a25d4a4c74baecbc Mon Sep 17 00:00:00 2001 From: Khafra <42794878+KhafraDev@users.noreply.github.com> Date: Wed, 20 Jul 2022 18:10:21 -0400 Subject: [PATCH 1/2] fix: add `isErrorLike` --- lib/fetch/index.js | 7 ++--- lib/fetch/response.js | 15 ++++++----- lib/fetch/util.js | 10 ++++++- test/jest/instanceof-error.test.js | 43 ++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 test/jest/instanceof-error.test.js diff --git a/lib/fetch/index.js b/lib/fetch/index.js index 4713ab7eb25..f9b09547edb 100644 --- a/lib/fetch/index.js +++ b/lib/fetch/index.js @@ -33,7 +33,8 @@ const { isBlobLike, sameOrigin, isCancelled, - isAborted + isAborted, + isErrorLike } = require('./util') const { kState, kHeaders, kGuard, kRealm } = require('./symbols') const assert = require('assert') @@ -1854,7 +1855,7 @@ async function httpNetworkFetch ( timingInfo.decodedBodySize += bytes?.byteLength ?? 0 // 6. If bytes is failure, then terminate fetchParams’s controller. - if (bytes instanceof Error) { + if (isErrorLike(bytes)) { fetchParams.controller.terminate(bytes) return } @@ -1894,7 +1895,7 @@ async function httpNetworkFetch ( // 3. Otherwise, if stream is readable, error stream with a TypeError. if (isReadable(stream)) { fetchParams.controller.controller.error(new TypeError('terminated', { - cause: reason instanceof Error ? reason : undefined + cause: isErrorLike(reason) ? reason : undefined })) } } diff --git a/lib/fetch/response.js b/lib/fetch/response.js index 4649a5da907..526259478d4 100644 --- a/lib/fetch/response.js +++ b/lib/fetch/response.js @@ -10,7 +10,8 @@ const { isCancelled, isAborted, isBlobLike, - serializeJavascriptValueToJSONString + serializeJavascriptValueToJSONString, + isErrorLike } = require('./util') const { redirectStatus, @@ -347,15 +348,15 @@ function makeResponse (init) { } function makeNetworkError (reason) { + const isError = isErrorLike(reason) return makeResponse({ type: 'error', status: 0, - error: - reason instanceof Error - ? reason - : new Error(reason ? String(reason) : reason, { - cause: reason instanceof Error ? reason : undefined - }), + error: isError + ? reason + : new Error(reason ? String(reason) : reason, { + cause: isError ? reason : undefined + }), aborted: reason && reason.name === 'AbortError' }) } diff --git a/lib/fetch/util.js b/lib/fetch/util.js index 17c68162980..9806e331871 100644 --- a/lib/fetch/util.js +++ b/lib/fetch/util.js @@ -82,6 +82,13 @@ function isFileLike (object) { ) } +function isErrorLike (object) { + return object instanceof Error || ( + object?.constructor?.name === 'Error' || + object?.constructor?.name === 'DOMException' + ) +} + // Check whether |statusText| is a ByteString and // matches the Reason-Phrase token production. // RFC 2616: https://tools.ietf.org/html/rfc2616 @@ -469,5 +476,6 @@ module.exports = { makeIterator, isValidHeaderName, isValidHeaderValue, - hasOwn + hasOwn, + isErrorLike } diff --git a/test/jest/instanceof-error.test.js b/test/jest/instanceof-error.test.js new file mode 100644 index 00000000000..47e22050e7c --- /dev/null +++ b/test/jest/instanceof-error.test.js @@ -0,0 +1,43 @@ +'use strict' + +const { createServer } = require('http') +const { once } = require('events') + +/* global expect, it, jest, AbortController */ + +// https://github.com/facebook/jest/issues/11607#issuecomment-899068995 +jest.useRealTimers() + +const runIf = (condition) => condition ? it : it.skip +const nodeMajor = Number(process.versions.node.split('.', 1)[0]) + +runIf(nodeMajor >= 16)('isErrorLike sanity check', () => { + const { isErrorLike } = require('../../lib/fetch/util') + const error = new DOMException('') + + // https://github.com/facebook/jest/issues/2549 + expect(error instanceof Error).toBeFalsy() + expect(isErrorLike(error)).toBeTruthy() +}) + +runIf(nodeMajor >= 16)('Real use-case', async () => { + const { fetch } = require('../..') + + const ac = new AbortController() + ac.abort() + + const server = createServer((req, res) => { + res.end() + }).listen(0) + + await once(server, 'listening') + + const promise = fetch(`https://localhost:${server.address().port}`, { + signal: ac.signal + }) + + await expect(promise).rejects.toThrowError('The operation was aborted.') + + server.close() + await once(server, 'close') +}) From ea54ea1d29fd9916820e5e17baaabd3a115039c4 Mon Sep 17 00:00:00 2001 From: Khafra <42794878+KhafraDev@users.noreply.github.com> Date: Wed, 20 Jul 2022 18:35:06 -0400 Subject: [PATCH 2/2] fix: DOMException is not globally available in v16 --- test/jest/instanceof-error.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/jest/instanceof-error.test.js b/test/jest/instanceof-error.test.js index 47e22050e7c..0422bfac76b 100644 --- a/test/jest/instanceof-error.test.js +++ b/test/jest/instanceof-error.test.js @@ -13,6 +13,7 @@ const nodeMajor = Number(process.versions.node.split('.', 1)[0]) runIf(nodeMajor >= 16)('isErrorLike sanity check', () => { const { isErrorLike } = require('../../lib/fetch/util') + const { DOMException } = require('../../lib/fetch/constants') const error = new DOMException('') // https://github.com/facebook/jest/issues/2549