mirror of
https://github.com/elysiajs/elysia
synced 2026-08-09 12:35:48 +00:00
Merge pull request #1679 from raunak-rpm/fix/async-generator-headers-1677
fix: preserve headers when throwing from AsyncGenerator
This commit is contained in:
+14
-2
@@ -374,7 +374,14 @@ export const isAsync = (v: Function | HookContainer) => {
|
||||
if (isObject && v.isAsync !== undefined) return v.isAsync
|
||||
|
||||
const fn = isObject ? v.fn : v
|
||||
if (fn.constructor.name === 'AsyncFunction') return true
|
||||
// Check for both AsyncFunction and AsyncGeneratorFunction
|
||||
// AsyncGeneratorFunction needs to be treated as async because generator.next()
|
||||
// returns a Promise that may reject (e.g., when throwing from the generator)
|
||||
if (
|
||||
fn.constructor.name === 'AsyncFunction' ||
|
||||
fn.constructor.name === 'AsyncGeneratorFunction'
|
||||
)
|
||||
return true
|
||||
|
||||
const literal: string = fn.toString()
|
||||
|
||||
@@ -862,7 +869,12 @@ export const composeHandler = ({
|
||||
|
||||
const mapResponse = (r = 'r') => {
|
||||
const after = afterResponse()
|
||||
const response = `${hasSet ? 'mapResponse' : 'mapCompactResponse'}(${saveResponse}${r}${hasSet ? ',c.set' : ''}${mapResponseContext})\n`
|
||||
// When maybeStream is true, mapResponse may return a Promise (from handleStream)
|
||||
// that can reject if the generator throws. We need to await it so the try-catch
|
||||
// can properly catch the rejection and route it to error handling.
|
||||
// Only add await if the function is async (maybeAsync), otherwise it would be a syntax error.
|
||||
const awaitStream = maybeStream && maybeAsync ? 'await ' : ''
|
||||
const response = `${awaitStream}${hasSet ? 'mapResponse' : 'mapCompactResponse'}(${saveResponse}${r}${hasSet ? ',c.set' : ''}${mapResponseContext})\n`
|
||||
|
||||
if (!after) return `return ${response}`
|
||||
|
||||
|
||||
@@ -561,6 +561,49 @@ describe('Stream', () => {
|
||||
expect(response.headers.get('content-type')).toBe('text/event-stream')
|
||||
})
|
||||
|
||||
// Issue #1677: Throwing from AsyncGenerator should preserve headers
|
||||
it('should preserve headers when throwing from async generator', async () => {
|
||||
const { status: statusFn } = await import('../../src')
|
||||
|
||||
const app = new Elysia().get('/', async function* ({ set }) {
|
||||
set.headers['access-control-allow-origin'] = '*'
|
||||
set.headers['x-custom-header'] = 'test-value'
|
||||
// Throw before yielding - this is the bug scenario from #1677
|
||||
if (true) throw statusFn(500)
|
||||
yield 'unreachable'
|
||||
})
|
||||
|
||||
const response = await app.handle(req('/'))
|
||||
|
||||
expect(response.status).toBe(500)
|
||||
expect(response.headers.get('access-control-allow-origin')).toBe('*')
|
||||
expect(response.headers.get('x-custom-header')).toBe('test-value')
|
||||
})
|
||||
|
||||
// Issue #1677: onError hook should be called when throwing from generator
|
||||
it('should call onError hook when throwing from async generator', async () => {
|
||||
const { status: statusFn } = await import('../../src')
|
||||
let onErrorCalled = false
|
||||
let errorCode: string | number | undefined
|
||||
|
||||
const app = new Elysia()
|
||||
.onError(({ code }) => {
|
||||
onErrorCalled = true
|
||||
errorCode = code
|
||||
})
|
||||
.get('/', async function* ({ set }) {
|
||||
set.headers['x-custom-header'] = 'test-value'
|
||||
// Throw before yielding - this is the bug scenario from #1677
|
||||
if (true) throw statusFn(500)
|
||||
yield 'unreachable'
|
||||
})
|
||||
|
||||
const response = await app.handle(req('/'))
|
||||
|
||||
expect(response.status).toBe(500)
|
||||
expect(onErrorCalled).toBe(true)
|
||||
expect(errorCode).toBe(500)
|
||||
expect(response.headers.get('x-custom-header')).toBe('test-value')
|
||||
it('handle sse with plugin global hooks and trace', async () => {
|
||||
const PluginA = () =>
|
||||
new Elysia({ name: 'PluginA' })
|
||||
|
||||
Reference in New Issue
Block a user