Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add isErrorLike #1570

Merged
merged 2 commits into from Jul 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 4 additions & 3 deletions lib/fetch/index.js
Expand Up @@ -33,7 +33,8 @@ const {
isBlobLike,
sameOrigin,
isCancelled,
isAborted
isAborted,
isErrorLike
} = require('./util')
const { kState, kHeaders, kGuard, kRealm } = require('./symbols')
const assert = require('assert')
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}))
}
}
Expand Down
15 changes: 8 additions & 7 deletions lib/fetch/response.js
Expand Up @@ -10,7 +10,8 @@ const {
isCancelled,
isAborted,
isBlobLike,
serializeJavascriptValueToJSONString
serializeJavascriptValueToJSONString,
isErrorLike
} = require('./util')
const {
redirectStatus,
Expand Down Expand Up @@ -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'
})
}
Expand Down
10 changes: 9 additions & 1 deletion lib/fetch/util.js
Expand Up @@ -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
Expand Down Expand Up @@ -469,5 +476,6 @@ module.exports = {
makeIterator,
isValidHeaderName,
isValidHeaderValue,
hasOwn
hasOwn,
isErrorLike
}
44 changes: 44 additions & 0 deletions test/jest/instanceof-error.test.js
@@ -0,0 +1,44 @@
'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 { DOMException } = require('../../lib/fetch/constants')
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')
})