“TypeError: fetch failed” 是 Node 18 以后最让人摸不着头脑的报错,因为 message 里什么信息都没有。这篇在本机用小服务复现一系列失败(拒绝连接、断开、证书、超时、域名不存在),把每一种在 e.cause 里的样子列出来,再给一个按 cause.code 判断的重试写法。
node s1.js(退出码 1):
typeof e = TypeError | e.message = "fetch failed" | e.code = undefined
e.cause.constructor = Error | e.cause.message = connect ECONNREFUSED 127.0.0.1:<port>
e.cause.code = ECONNREFUSED | errno = -61 | syscall = connect | address = 127.0.0.1 | port = <port>
node:internal/process/promises:324
triggerUncaughtException(err, true /* fromPromise */);
^
[TypeError: fetch failed] {
[cause]: Error: connect ECONNREFUSED 127.0.0.1:9770
at TCPConnectWrap.afterConnect [as oncomplete] (node:net:2083:16) {
errno: -61,
code: 'ECONNREFUSED',
syscall: 'connect',
address: '127.0.0.1',
port: 9770
}
}
Node.js v26.12.0外层永远是 TypeError: fetch failed,线索在 cause;日志里要把 cause 一起记下来。
node s2.js:
127.0.0.1 -> TypeError: fetch failed (0.02 s)
cause: Error | connect ECONNREFUSED 127.0.0.1:<port> | code=ECONNREFUSED syscall=connect address=127.0.0.1
localhost -> fetch failed | cause = AggregateError | code = ECONNREFUSED | message = ""
errors[0]: connect ECONNREFUSED ::1:<port> | address = ::1
errors[1]: connect ECONNREFUSED 127.0.0.1:<port> | address = 127.0.0.1localhost 解析出多个地址,失败时 cause 是 AggregateError,要读 errors。
node s3.js:
服务端收到连接就关闭 -> TypeError: fetch failed (0.02 s)
cause: Error | read ECONNRESET | code=ECONNRESET syscall=read
服务端读到请求后正常关闭(FIN) -> TypeError: fetch failed (0.00 s)
cause: SocketError | other side closed | code=UND_ERR_SOCKET
服务端在请求后发 RST -> TypeError: fetch failed (0.00 s)
cause: Error | read ECONNRESET | code=ECONNRESET syscall=read
服务端回的不是 HTTP -> TypeError: fetch failed (0.00 s)
cause: HTTPParserError | Response does not match the HTTP/1.1 protocol (Expected HTTP/, RTSP/ or ICE/) | code=undefined
https 自签名证书 -> TypeError: fetch failed (0.00 s)
cause: Error | self-signed certificate; if the root CA is installed locally, try running Node.js with --use-system-ca | code=DEPTH_ZERO_SELF_SIGNED_CERT
https 访问 http 端口 -> TypeError: fetch failed (0.00 s)
cause: Error | read ECONNRESET | code=ECONNRESET syscall=read
域名不存在(.invalid) -> TypeError: fetch failed (0.01 s)
cause: Error | getaddrinfo ENOTFOUND nonexistent-host.invalid | code=ENOTFOUND syscall=getaddrinfo
URL 缺协议 -> TypeError: fetch failed (0.00 s)
cause: Error | unknown scheme | code=undefined
URL 不合法 -> TypeError: Failed to parse URL from not a url (0.00 s)
cause: TypeError | Invalid URL | code=ERR_INVALID_URL断开、重置、非 HTTP 响应、证书、DNS、协议缺失,各自的 cause 不同。
node s4.js:
不设超时:3 秒后仍然挂着(等了 3.0 s)
AbortSignal.timeout(500) -> TimeoutError: The operation was aborted due to timeout (0.50 s)
手动 AbortController.abort() -> AbortError: This operation was aborted (0.30 s)
TimeoutError 的 name = TimeoutError | instanceof TypeError = false | code = 23 | cause = undefined不设超时会一直挂着;AbortSignal.timeout 得到 TimeoutError。
node s5.js:
第 1 次失败:fetch failed | 原因 ECONNREFUSED
第 2 次失败:fetch failed | 原因 ECONNREFUSED
第 3 次失败:fetch failed | 原因 ECONNREFUSED
成功:HTTP 200 hello(0.48 s)按 cause.code(含 AggregateError)判断,不要按 message。