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

fetch(..., { signal: AbortSignal.timeout(...) }) throws AbortError instead of TimeoutError when timeout happens #1718

Closed
Frederick888 opened this issue Feb 3, 2025 · 2 comments · Fixed by #1729
Labels
bug Something isn't working

Comments

@Frederick888
Copy link

Describe the bug
When fetch() is aborted due to a timeout signal, the DOMException's name is AbortError instead of TimeoutError.

To Reproduce
I'm using happy-dom in Jest to test some code similar to the MDN example https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/any_static#using_abortsignal.any.

  • Node.js v22.12.0
  • happy-dom v16.8.1
  • Jest v29.7.0
  • happy-com/jest-environment v16.8.1
  • ts-jest v29.1.1
/**
 * @jest-environment @happy-dom/jest-environment
 * @jest-environment-options {"settings": {"fetch": {"disableSameOriginPolicy": true} } }
 */

import { setTimeout } from 'timers/promises'

it('happy-dom', async () => {
  try {
    const signal = AbortSignal.timeout(20)
    signal.addEventListener('abort', (ev) => {
      const currentTarget = ev.currentTarget as typeof ev.currentTarget & {
        reason: DOMException
      }
      console.debug({ reason: currentTarget.reason.name })
    })
    await setTimeout(100)
    await fetch('https://example.com', { signal })
  } catch (e) {
    console.debug(e)
    expect((e as Error).name).toStrictEqual('TimeoutError')
  }
})

Output:

  console.debug  <-- log in signal.AddEventListener()
    { reason: 'TimeoutError' }

      at AbortSignal.<anonymous> (src/shared/hooks/http/fetch.test.ts:40:15)

  console.debug  <-- log in catch
    DOMException [AbortError]: The operation was aborted.
        at Fetch.send (/path/to/project/node_modules/happy-dom/src/fetch/Fetch.ts:139:10)
        at Window.fetch (/path/to/project/node_modules/happy-dom/src/window/BrowserWindow.ts:1540:10)

      at src/shared/hooks/http/fetch.test.ts:45:13
          at Generator.throw (<anonymous>)

 FAIL  src/shared/hooks/http/fetch.test.ts (7.052 s)
  ✕ whatwg-fetch (133 ms)

  ● whatwg-fetch

    expect(received).toStrictEqual(expected) // deep equality

    Expected: "TimeoutError"
    Received: "AbortError"

      44 |   } catch (e) {
      45 |     console.debug(e)
    > 46 |     expect((e as Error).name).toStrictEqual('TimeoutError')
         |                               ^
      47 |   }
      48 | })
      49 |

      at src/shared/hooks/http/fetch.test.ts:46:31
          at Generator.throw (<anonymous>)
      at rejected (src/shared/hooks/http/fetch.test.ts:10:65)

Expected behavior
It throws a DOMException whose name is TimeoutError and message is The operation timed out. (like Firefox) or signal timed out (like Chrome).

Screenshots
N/A

Device:

  • OS: macOS
  • Browser: N/A
  • Version: 15.2

Additional context
Add any other context about the problem here.

@Frederick888 Frederick888 added the bug Something isn't working label Feb 3, 2025
@Frederick888
Copy link
Author

Also if a custom abort reason is used, it should copy it too:

it('happy-dom', async () => {
  try {
    const c = new AbortController()
    setTimeout(() => {
      c.abort('Foo')
    }, 10)
    await fetch('https://example.com', { signal: c.signal })
  } catch (e) {
    expect(e).toStrictEqual('Foo')
  }
})

Output:

  console.error
    Error: socket hang up
        at TLSSocket.socketCloseListener (node:_http_client:485:27)
        at TLSSocket.emit (node:events:536:35)
        at node:net:350:12
        at TCP.done (node:_tls_wrap:650:7) {
      code: 'ECONNRESET'
    }

      at Fetch.onError (node_modules/happy-dom/src/fetch/Fetch.ts:599:37)

 FAIL  src/shared/hooks/http/fetch.test.ts (16.882 s)
  ✕ happy-dom (131 ms)

  ● happy-dom

    expect(received).toStrictEqual(expected) // deep equality

    Expected: "Foo"
    Received: [AbortError: The operation was aborted. Foo]

      38 |     await fetch('https://example.com', { signal: c.signal })
      39 |   } catch (e) {
    > 40 |     expect(e).toStrictEqual('Foo')
         |               ^
      41 |   }
      42 | })
      43 |

      at src/shared/hooks/http/fetch.test.ts:40:15
          at Generator.throw (<anonymous>)
      at rejected (src/shared/hooks/http/fetch.test.ts:10:65)

@capricorn86
Copy link
Owner

Thank you for reporting @Frederick888! 🙂

Thanks to @btea, there is a fix in now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
2 participants