-
-
Notifications
You must be signed in to change notification settings - Fork 435
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(jsx): Add catch to async function's promise #2471
Conversation
The `.then(async function())` returns a new promise, that the above .catch() does not catch, resulting in rapid refreshing of the page to cause a fatal uncatchable error. Adding this second .catch fixes that.
Hi @mwilkins91 Thanks for making the pull request. There are indeed some issues you point out, which I think will be fixed in this PR. Please wait a bit so I can look at the whole thing and comment. |
@usualoma Thanks! Let me know if I can help. |
Hi @mwilkins91 ! I consider the following
Given these assumptions, I would suggest the following patches to this matter. If you have no objection, I would be happy to have this change incorporated into the PR. diff --git a/src/jsx/streaming.test.tsx b/src/jsx/streaming.test.tsx
index 0c9ba6e1..c5cd5579 100644
--- a/src/jsx/streaming.test.tsx
+++ b/src/jsx/streaming.test.tsx
@@ -390,10 +390,12 @@ d.replaceWith(c.content)
return <p>{content}</p>
}
+ const onError = vi.fn()
const stream = renderToReadableStream(
<Suspense fallback={<p>Loading...</p>}>
<Content />
- </Suspense>
+ </Suspense>,
+ onError
)
const chunks = []
@@ -402,6 +404,8 @@ d.replaceWith(c.content)
chunks.push(textDecoder.decode(chunk))
}
+ expect(onError).toBeCalledTimes(1)
+
expect(chunks).toEqual([
`<template id="H:${suspenseCounter}"></template><p>Loading...</p><!--/$-->`,
'',
@@ -412,6 +416,55 @@ d.replaceWith(c.content)
)
})
+ it('closed()', async () => {
+ const Content = async () => {
+ await new Promise<void>((resolve) =>
+ setTimeout(() => {
+ vi.spyOn(ReadableStreamDefaultController.prototype, 'enqueue').mockImplementation(() => {
+ throw new Error('closed')
+ })
+ resolve()
+ }, 10)
+ )
+ return <p>content</p>
+ }
+
+ const onError = vi.fn()
+ const stream = renderToReadableStream(
+ <>
+ <Suspense fallback={<p>Loading...</p>}>
+ <Content />
+ </Suspense>
+ <Suspense fallback={<p>Loading...</p>}>
+ <Content />
+ </Suspense>
+ </>,
+ onError
+ )
+
+ const chunks = []
+ const textDecoder = new TextDecoder()
+ for await (const chunk of stream as any) {
+ chunks.push(textDecoder.decode(chunk))
+ }
+
+ expect(onError).toBeCalledTimes(1)
+
+ expect(chunks).toEqual([
+ `<template id="H:${suspenseCounter}"></template><p>Loading...</p><!--/$--><template id="H:${
+ suspenseCounter + 1
+ }"></template><p>Loading...</p><!--/$-->`,
+ ])
+
+ expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(
+ '<p>Loading...</p><!--/$--><p>Loading...</p><!--/$-->'
+ )
+
+ suspenseCounter++
+ await new Promise((resolve) => setTimeout(resolve, 10))
+ vi.restoreAllMocks()
+ })
+
it('Multiple "await" call', async () => {
const delayedContent = new Promise<HtmlEscapedString>((resolve) =>
setTimeout(() => resolve(<h1>Hello</h1>), 10)
diff --git a/src/jsx/streaming.ts b/src/jsx/streaming.ts
index fac77193..5df7a3e4 100644
--- a/src/jsx/streaming.ts
+++ b/src/jsx/streaming.ts
@@ -116,48 +116,60 @@ const textEncoder = new TextEncoder()
* The API might be changed.
*/
export const renderToReadableStream = (
- str: HtmlEscapedString | Promise<HtmlEscapedString>
+ str: HtmlEscapedString | Promise<HtmlEscapedString>,
+ onError: (e: unknown) => void = console.trace
): ReadableStream<Uint8Array> => {
const reader = new ReadableStream<Uint8Array>({
async start(controller) {
- const tmp = str instanceof Promise ? await str : await str.toString()
- const context = typeof tmp === 'object' ? tmp : {}
- const resolved = await resolveCallback(
- tmp,
- HtmlEscapedCallbackPhase.BeforeStream,
- true,
- context
- )
- controller.enqueue(textEncoder.encode(resolved))
-
- let resolvedCount = 0
- const callbacks: Promise<void>[] = []
- const then = (promise: Promise<string>) => {
- callbacks.push(
- promise
- .catch((err) => {
- console.trace(err)
- return ''
- })
- .then(async (res) => {
- res = await resolveCallback(res, HtmlEscapedCallbackPhase.BeforeStream, true, context)
- ;(res as HtmlEscapedString).callbacks
- ?.map((c) => c({ phase: HtmlEscapedCallbackPhase.Stream, context }))
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- .filter<Promise<string>>(Boolean as any)
- .forEach(then)
- resolvedCount++
- controller.enqueue(textEncoder.encode(res))
- })
+ try {
+ const tmp = str instanceof Promise ? await str : await str.toString()
+ const context = typeof tmp === 'object' ? tmp : {}
+ const resolved = await resolveCallback(
+ tmp,
+ HtmlEscapedCallbackPhase.BeforeStream,
+ true,
+ context
)
- }
- ;(resolved as HtmlEscapedString).callbacks
- ?.map((c) => c({ phase: HtmlEscapedCallbackPhase.Stream, context }))
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- .filter<Promise<string>>(Boolean as any)
- .forEach(then)
- while (resolvedCount !== callbacks.length) {
- await Promise.all(callbacks)
+ controller.enqueue(textEncoder.encode(resolved))
+
+ let resolvedCount = 0
+ const callbacks: Promise<void>[] = []
+ const then = (promise: Promise<string>) => {
+ callbacks.push(
+ promise
+ .catch((err) => {
+ console.log(err)
+ onError(err)
+ return ''
+ })
+ .then(async (res) => {
+ res = await resolveCallback(
+ res,
+ HtmlEscapedCallbackPhase.BeforeStream,
+ true,
+ context
+ )
+ ;(res as HtmlEscapedString).callbacks
+ ?.map((c) => c({ phase: HtmlEscapedCallbackPhase.Stream, context }))
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ .filter<Promise<string>>(Boolean as any)
+ .forEach(then)
+ resolvedCount++
+ controller.enqueue(textEncoder.encode(res))
+ })
+ )
+ }
+ ;(resolved as HtmlEscapedString).callbacks
+ ?.map((c) => c({ phase: HtmlEscapedCallbackPhase.Stream, context }))
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ .filter<Promise<string>>(Boolean as any)
+ .forEach(then)
+ while (resolvedCount !== callbacks.length) {
+ await Promise.all(callbacks)
+ }
+ } catch (e) {
+ // maybe the connection was closed
+ onError(e)
}
controller.close() |
That seems to make sense to me @usualoma! When you say |
Hi @mwilkins91! Thanks for the confirmation. |
Yes. @mwilkins91, can you apply the diff to this PR? If it's done, I'll merge it into main with @usualoma as a co-author. |
Sounds perfect, I'll do that tonight! |
@yusukebe done! |
@mwilkins91 Thanks! One thing. Can you run |
@mwilkins91 Yup. That's right! I'll merge this now. Thanks! |
[![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [hono](https://hono.dev/) ([source](https://togithub.com/honojs/hono)) | [`4.1.4` -> `4.2.7`](https://renovatebot.com/diffs/npm/hono/4.1.4/4.2.7) | [![age](https://developer.mend.io/api/mc/badges/age/npm/hono/4.2.7?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/hono/4.2.7?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/hono/4.1.4/4.2.7?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/hono/4.1.4/4.2.7?slim=true)](https://docs.renovatebot.com/merge-confidence/) | ### GitHub Vulnerability Alerts #### [CVE-2024-32869](https://togithub.com/honojs/hono/security/advisories/GHSA-3mpf-rcc7-5347) ### Summary When using serveStatic with deno, it is possible to directory traverse where main.ts is located. My environment is configured as per this tutorial https://hono.dev/getting-started/deno ### PoC ```bash $ tree . ├── deno.json ├── deno.lock ├── main.ts ├── README.md └── static └── a.txt ``` source ```jsx import { Hono } from 'https://deno.land/x/hono@v4.2.6/mod.ts' import { serveStatic } from 'https://deno.land/x/hono@v4.2.6/middleware.ts' const app = new Hono() app.use('/static/*', serveStatic({ root: './' })) Deno.serve(app.fetch) ``` request ```bash curl localhost:8000/static/%2e%2e/main.ts ``` response is content of main.ts ### Impact Unexpected files are retrieved. --- ### Release Notes <details> <summary>honojs/hono (hono)</summary> ### [`v4.2.7`](https://togithub.com/honojs/hono/releases/tag/v4.2.7) [Compare Source](https://togithub.com/honojs/hono/compare/v4.2.6...v4.2.7) This release fixes "[Restricted Directory Traversal in serveStatic with deno](https://togithub.com/honojs/hono/security/advisories/GHSA-3mpf-rcc7-5347)". **Full Changelog**: honojs/hono@v4.2.6...v4.2.7 ### [`v4.2.6`](https://togithub.com/honojs/hono/releases/tag/v4.2.6) [Compare Source](https://togithub.com/honojs/hono/compare/v4.2.5...v4.2.6) #### What's Changed - refactor(adapter/aws): Optimize multiple call of same conditions with polymorphism by [@​exoego](https://togithub.com/exoego) in [honojs/hono#2521 - fix(sse): close sse stream on end by [@​domeccleston](https://togithub.com/domeccleston) in [honojs/hono#2529 - fix(client): Don't show `$ws` when not used WebSockets by [@​nakasyou](https://togithub.com/nakasyou) in [honojs/hono#2532 - refactor(ssg): update utils.ts by [@​eltociear](https://togithub.com/eltociear) in [honojs/hono#2519 #### New Contributors - [@​domeccleston](https://togithub.com/domeccleston) made their first contribution in [honojs/hono#2529 - [@​eltociear](https://togithub.com/eltociear) made their first contribution in [honojs/hono#2519 **Full Changelog**: honojs/hono@v4.2.5...v4.2.6 ### [`v4.2.5`](https://togithub.com/honojs/hono/releases/tag/v4.2.5) [Compare Source](https://togithub.com/honojs/hono/compare/v4.2.4...v4.2.5) #### What's Changed - fix(client): Allow calling toString and valueOf on the proxy object by [@​ibash](https://togithub.com/ibash) in [honojs/hono#2510 - fix(adapter): handle multi value headers in AWS Lambda by [@​exoego](https://togithub.com/exoego) in [honojs/hono#2494 - fix(client): shuold not remove tailing slash from top-level URL by [@​yusukebe](https://togithub.com/yusukebe) in [honojs/hono#2523 - fix(jsx/dom): remove lookbehind assertion in event regexp by [@​usualoma](https://togithub.com/usualoma) in [honojs/hono#2524 #### New Contributors - [@​ibash](https://togithub.com/ibash) made their first contribution in [honojs/hono#2510 **Full Changelog**: honojs/hono@v4.2.4...v4.2.5 ### [`v4.2.4`](https://togithub.com/honojs/hono/releases/tag/v4.2.4) [Compare Source](https://togithub.com/honojs/hono/compare/v4.2.3...v4.2.4) ##### What's Changed - fix(jwt): Make JWT Header `typ` Field Optional to Enhance Compatibility by [@​naporin0624](https://togithub.com/naporin0624) in [honojs/hono#2488 - fix(testing): set `baseUrl` for `testClient` by [@​yusukebe](https://togithub.com/yusukebe) in [honojs/hono#2496 - fix(validator): Default use to `OutputTypeExcludeResponseType` when `InputType` is unknown by [@​nagasawaryoya](https://togithub.com/nagasawaryoya) in [honojs/hono#2500 - refactor(trie-router): parentPatterns is updated but never queried by [@​exoego](https://togithub.com/exoego) in [honojs/hono#2503 - refactor: Remove redundant initializer by [@​exoego](https://togithub.com/exoego) in [honojs/hono#2502 - refactor(cloudflare-workers): Suppress eslint noise by [@​exoego](https://togithub.com/exoego) in [honojs/hono#2504 - fix(jsx): Add catch to async function's promise by [@​mwilkins91](https://togithub.com/mwilkins91) in [honojs/hono#2471 ##### New Contributors - [@​nagasawaryoya](https://togithub.com/nagasawaryoya) made their first contribution in [honojs/hono#2500 - [@​exoego](https://togithub.com/exoego) made their first contribution in [honojs/hono#2503 - [@​mwilkins91](https://togithub.com/mwilkins91) made their first contribution in [honojs/hono#2471 **Full Changelog**: honojs/hono@v4.2.3...v4.2.4 ### [`v4.2.3`](https://togithub.com/honojs/hono/releases/tag/v4.2.3) [Compare Source](https://togithub.com/honojs/hono/compare/v4.2.2...v4.2.3) #### What's Changed - fix(ssg): use response header to mark as disabled routes for SSG by [@​usualoma](https://togithub.com/usualoma) in [honojs/hono#2477 - fix(trailing-slash): export types in `package.json` correctly by [@​yusukebe](https://togithub.com/yusukebe) in [honojs/hono#2483 - fix(client): fix websocket client protocol by [@​naporin0624](https://togithub.com/naporin0624) in [honojs/hono#2479 **Full Changelog**: honojs/hono@v4.2.2...v4.2.3 ### [`v4.2.2`](https://togithub.com/honojs/hono/releases/tag/v4.2.2) [Compare Source](https://togithub.com/honojs/hono/compare/v4.2.1...v4.2.2) #### What's Changed - feat(jsx-renderer): pass the context as 2nd arg by [@​yusukebe](https://togithub.com/yusukebe) in [honojs/hono#2459 - feat(client): accept a function that provides dynamic headers to hc by [@​niko-gardenanet](https://togithub.com/niko-gardenanet) in [honojs/hono#2461 - fix(client): infer `null` correctly by [@​yusukebe](https://togithub.com/yusukebe) in [honojs/hono#2469 #### New Contributors - [@​niko-gardenanet](https://togithub.com/niko-gardenanet) made their first contribution in [honojs/hono#2461 **Full Changelog**: honojs/hono@v4.2.1...v4.2.2 ### [`v4.2.1`](https://togithub.com/honojs/hono/releases/tag/v4.2.1) [Compare Source](https://togithub.com/honojs/hono/compare/v4.2.0...v4.2.1) #### What's Changed - fix(jws): Only import necessary helper (not all helpers) by [@​nicksrandall](https://togithub.com/nicksrandall) in [honojs/hono#2458 #### New Contributors - [@​nicksrandall](https://togithub.com/nicksrandall) made their first contribution in [honojs/hono#2458 **Full Changelog**: honojs/hono@v4.2.0...v4.2.1 ### [`v4.2.0`](https://togithub.com/honojs/hono/releases/tag/v4.2.0) [Compare Source](https://togithub.com/honojs/hono/compare/v4.1.7...v4.2.0) Hono v4.2.0 is now available! Let's take a look at the new features. #### Added more algorithms for JWT The number of algorithms that JWT util can handle has increased from only 3 to 13! This means that JWT util now implements many of the algorithms supported by JWT. - HS256 - HS384 - HS512 - RS256 - RS384 - RS512 - PS256 - PS384 - PS512 - ES256 - ES384 - ES512 - EdDSA You can use these algorithms from the JWT middleware or JWT helpers. Thanks [@​Code-Hex](https://togithub.com/Code-Hex)! #### Method Override Middleware [Method Override Middleware](https://hono.dev/middleware/builtin/method-override) has been added. This middleware override the method of the real request with the specified method. HTML `form` does not allow you to send a DELETE method request. Instead, by sending an input with `name` as `_method` and a value of `DELETE`, you can call the handler registered in `app.delete()`. ```ts const app = new Hono() // If no options are specified, the value of `_method` in the form, // e.g. DELETE, is used as the method. app.use('/posts', methodOverride({ app })) app.delete('/posts', (c) => { // .... }) ``` #### Trailing Slash Middleware [Trailing Slash Middleware](https://hono.dev/middleware/builtin/trailing-slash) resolves the handling of Trailing Slashes in GET requests. You can use `appendTrailingSlash` and `trimTrailingSlash` functions. For example, it redirects a GET request to `/about/me` to `/about/me/`. ```ts import { Hono } from 'hono' import { appendTrailingSlash } from 'hono/trailing-slash' const app = new Hono({ strict: true }) app.use(appendTrailingSlash()) app.get('/about/me/', (c) => c.text('With Trailing Slash')) ``` Thanks [@​rnmeow](https://togithub.com/rnmeow)! #### Other features - SSG Helper - Support `extensionMap` [honojs/hono#2382 - JSX/DOM - Add `userId` hook [honojs/hono#2389 - JWT Middleware - Improve error handling [honojs/hono#2406 - Request - Cache the body for re-using [honojs/hono#2416 - JWT Util - Add type helper to `payload` [honojs/hono#2424 - CORS Middleware - Pass context to `options.origin` function [honojs/hono#2436 - Cache Middleware - Support for the `vary` header option [honojs/hono#2426 - HTTP Exception - Add `cause` option [honojs/hono#2224 - Logger - Support `NO_COLOR` [honojs/hono#2228 - JWT Middleware - Add `JwtTokenInvalid` object as `cause` when JWT is invalid [honojs/hono#2448 - Bearer Auth Middleware - Add `verifyToken` option [honojs/hono#2449 - Basic Auth Middleware - Add `verifyUser` option [honojs/hono#2450 #### All Updates - feat(jwt): supported RS256, RS384, RS512 algorithm for JWT by [@​Code-Hex](https://togithub.com/Code-Hex) in [honojs/hono#2339 - added remain algorithm for JWT by [@​Code-Hex](https://togithub.com/Code-Hex) in [honojs/hono#2352 - acceptable CryptoKey in JWT sign and verify by [@​Code-Hex](https://togithub.com/Code-Hex) in [honojs/hono#2373 - feat(ssg): Support `extentionMap` by [@​watany-dev](https://togithub.com/watany-dev) in [honojs/hono#2382 - feat(jwt): support remaining algorithms by [@​yusukebe](https://togithub.com/yusukebe) in [honojs/hono#2368 - feat(jsx): add useId hook by [@​usualoma](https://togithub.com/usualoma) in [honojs/hono#2389 - feat(middleware/jwt): improve error handling by [@​tfkhdyt](https://togithub.com/tfkhdyt) in [honojs/hono#2406 - feat(request): cache body for reusing by [@​yusukebe](https://togithub.com/yusukebe) in [honojs/hono#2416 - feat(jwt): Add type helper to `payload` by [@​nakasyou](https://togithub.com/nakasyou) in [honojs/hono#2424 - feat: introduce Method Override Middleware by [@​yusukebe](https://togithub.com/yusukebe) in [honojs/hono#2420 - feat(middleware/cors): pass context to options.origin function by [@​okmr-d](https://togithub.com/okmr-d) in [honojs/hono#2436 - feat: support for `vary` header in cache middleware by [@​naporin0624](https://togithub.com/naporin0624) in [honojs/hono#2426 - feat: add middlewares resolve trailing slashes on GET request by [@​rnmeow](https://togithub.com/rnmeow) in [honojs/hono#2408 - test: stub `crypto` if not exist by [@​yusukebe](https://togithub.com/yusukebe) in [honojs/hono#2445 - feat(jwt): literal typed `alg` option value by [@​yusukebe](https://togithub.com/yusukebe) in [honojs/hono#2446 - test(ssg): add test for content-type includes `;` by [@​yusukebe](https://togithub.com/yusukebe) in [honojs/hono#2447 - feat(jwt): add `JwtTokenInvalid` object as `cause` when JWT is invalid by [@​yusukebe](https://togithub.com/yusukebe) in [honojs/hono#2448 - feat(bearer-auth): add `verifyToken` option by [@​yusukebe](https://togithub.com/yusukebe) in [honojs/hono#2449 - feat(basic-auth): add `verifyUser` option by [@​yusukebe](https://togithub.com/yusukebe) in [honojs/hono#2450 - Next by [@​yusukebe](https://togithub.com/yusukebe) in [honojs/hono#2454 #### New Contributors - [@​tfkhdyt](https://togithub.com/tfkhdyt) made their first contribution in [honojs/hono#2406 - [@​okmr-d](https://togithub.com/okmr-d) made their first contribution in [honojs/hono#2436 - [@​naporin0624](https://togithub.com/naporin0624) made their first contribution in [honojs/hono#2426 - [@​rnmeow](https://togithub.com/rnmeow) made their first contribution in [honojs/hono#2408 **Full Changelog**: honojs/hono@v4.1.7...v4.2.0 ### [`v4.1.7`](https://togithub.com/honojs/hono/releases/tag/v4.1.7) [Compare Source](https://togithub.com/honojs/hono/compare/v4.1.6...v4.1.7) #### What's Changed - fix(cache): check `globalThis.caches` by [@​yusukebe](https://togithub.com/yusukebe) in [honojs/hono#2444 **Full Changelog**: honojs/hono@v4.1.6...v4.1.7 ### [`v4.1.6`](https://togithub.com/honojs/hono/releases/tag/v4.1.6) [Compare Source](https://togithub.com/honojs/hono/compare/v4.1.5...v4.1.6) #### What's Changed - chore(benchmark): add "loop" script by [@​yusukebe](https://togithub.com/yusukebe) in [honojs/hono#2431 - fix(cache): not enabled if `caches` is not defined by [@​yusukebe](https://togithub.com/yusukebe) in [honojs/hono#2443 **Full Changelog**: honojs/hono@v4.1.5...v4.1.6 ### [`v4.1.5`](https://togithub.com/honojs/hono/releases/tag/v4.1.5) [Compare Source](https://togithub.com/honojs/hono/compare/v4.1.4...v4.1.5) #### What's Changed - perf: Don't use `Arrap.prototype.map` if it is not needed return value by [@​nakasyou](https://togithub.com/nakasyou) in [honojs/hono#2419 - fix(aws-lambda): handle response without body ([#​2401](https://togithub.com/honojs/hono/issues/2401)) by [@​KnisterPeter](https://togithub.com/KnisterPeter) in [honojs/hono#2413 - fix(validator): `await` cached contents by [@​yusukebe](https://togithub.com/yusukebe) in [honojs/hono#2430 #### New Contributors - [@​KnisterPeter](https://togithub.com/KnisterPeter) made their first contribution in [honojs/hono#2413 **Full Changelog**: honojs/hono@v4.1.4...v4.1.5 </details> --- ### Configuration 📅 **Schedule**: Branch creation - "" in timezone America/Chicago, Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/autoblocksai/cli). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy4zMTMuMSIsInVwZGF0ZWRJblZlciI6IjM3LjMxMy4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
The
.then(async function())
returns a new promise, that the above .catch() does not catch, resulting in rapid refreshing of the page to cause a fatal uncatchable error. Adding this second .catch fixes that.The problem:
If you're streaming a response, via renderToReadableStream from hono/jsx/streaming, and you rapidly refresh your page, you can trigger an error:
which is uncatchable as far as I can tell.
The fix:
Since the catch block above is applied to the
promise
variable, but not to the promise returned by the async function in the .then(), the call to enqueue on line 150 can fail due to a closed connection.Fixing it is as simple as adding another .catch, as funny as it looks at first glance.