Lifecycle Events
Keq provides an event mechanism that allows you to listen to various stages of the request lifecycle for logging, performance monitoring, debugging, and more.
Basic Usage
Use the .on() method to listen to events:
import { request } from 'keq'
await request
.get('/cats')
.on('fetch:before', ({ context }) => {
console.log('About to send request:', context.request.url)
})
.on('fetch:after', ({ context }) => {
console.log('Request completed:', context.response?.status)
})Instance-Level Event Listening
In addition to listening on individual requests, you can register event listeners on a KeqRequest instance for global event handling:
import { request } from 'keq'
// Listen for errors on all requests
request.on('error', ({ context }) => {
console.error('Request failed:', context.request.__url__.href)
})
// Listen for timeouts on all requests
request.on('timeout', ({ context }) => {
console.warn('Request timed out:', context.request.__url__.href)
})
// All subsequent requests will trigger the listeners above
await request.get('/cats')
await request.get('/dogs')The event system is designed for observation: the context in callbacks is read-only, and callbacks do not block request execution.
If you need to modify requests or responses, use middleware.
Built-in Events
fetch:before
Triggered before the fetch request is sent.
Event parameters:
| Parameter | Type | Description |
|---|---|---|
| context | KeqExecutionContext | Request execution context |
import { request } from 'keq'
await request
.get('/cats')
.on('fetch:before', ({ context }) => {
console.log('Request URL:', context.request.url)
console.log('Request method:', context.request.method)
})fetch:after
Triggered after the fetch request completes.
Event parameters:
| Parameter | Type | Description |
|---|---|---|
| context | KeqExecutionContext | Request execution context |
import { request } from 'keq'
await request
.get('/cats')
.on('fetch:after', ({ context }) => {
console.log('Response status:', context.response?.status)
})middleware:before
Triggered before middleware execution begins.
Event parameters:
| Parameter | Type | Description |
|---|---|---|
| context | KeqExecutionContext | Request execution context |
import { request } from 'keq'
await request
.get('/cats')
.on('middleware:before', ({ context }) => {
console.log('About to execute middleware')
})middleware:after
Triggered after middleware execution completes.
Event parameters:
| Parameter | Type | Description |
|---|---|---|
| context | KeqExecutionContext | Request execution context |
import { request } from 'keq'
await request
.get('/cats')
.on('middleware:after', ({ context }) => {
console.log('Middleware execution completed')
})retry
Triggered when a request is retried.
Event parameters:
| Parameter | Type | Description |
|---|---|---|
| context | KeqSharedContext | Request shared context |
import { request } from 'keq'
await request
.get('/cats')
.retry(3, 1000)
.on('retry', ({ context }) => {
console.log('Retrying request:', context.request.url)
})timeout
Triggered when a request times out.
Event parameters:
| Parameter | Type | Description |
|---|---|---|
| context | KeqExecutionContext | Request execution context |
import { request, TimeoutException } from 'keq'
try {
await request
.get('/cats')
.timeout(3000)
.on('timeout', ({ context }) => {
console.log('Request timed out:', context.request.url)
})
} catch (err) {
if (err instanceof TimeoutException) {
console.error('Request has timed out')
}
}abort
Triggered when a request is aborted.
Event parameters:
| Parameter | Type | Description |
|---|---|---|
| context | KeqSharedContext | Request shared context |
| reason | any | Abort reason |
import { request, AbortException, KeqMiddleware } from 'keq'
function autoAbort(): KeqMiddleware {
return async (context, next) => {
setTimeout(() => {
context.request.abort(new AbortException('Manually aborted request'))
}, 3000)
await next()
}
}
try {
await request
.get('/cats')
.use(autoAbort())
.on('abort', ({ context, reason }) => {
if (reason instanceof AbortException) {
console.log('Request aborted:', reason.message)
} else {
console.log('Request aborted:', reason)
}
})
} catch (err) {
if (err instanceof AbortException) {
console.error('Request was aborted:', err.message)
}
}error
Triggered when an error occurs during the request.
Event parameters:
| Parameter | Type | Description |
|---|---|---|
| context | KeqSharedContext | Request shared context |
import { request } from 'keq'
try {
await request
.get('/cats')
.on('error', ({ context }) => {
console.error('Request error occurred:', context.request.url)
})
} catch (err) {
console.error('Caught error:', err)
}Custom Events
You can define and trigger custom events for more flexible feature extensions.
Defining Custom Event Types
import { KeqContext } from 'keq'
declare module 'keq' {
interface KeqEvents {
'custom:event': { context: KeqContext; data: string }
}
}Triggering Custom Events
import { request, KeqMiddleware } from 'keq'
function emitCustomEvent(): KeqMiddleware {
return async (context, next) => {
// Trigger custom event before request
context.emitter.emit('custom:event', {
context,
data: 'custom data'
})
await next()
}
}
await request
.get('/cats')
.use(emitCustomEvent())
.on('custom:event', ({ context, data }) => {
console.log('Custom event triggered:', data)
})Cleaning Up Event Listeners
import { request, KeqMiddleware } from 'keq'
function eventListenerMiddleware(): KeqMiddleware {
return async (context, next) => {
function onCustomEvent({ context, data }) {
console.log('Received custom event:', data)
}
// Listen to event
context.emitter.on('custom:event', onCustomEvent)
await next()
// Manually clean up event listener (optional — automatically removed after request ends)
context.emitter.off('custom:event', onCustomEvent)
}
}Event listeners are automatically cleaned up after the request ends, so you typically don't need to manually call .off(). However, in some scenarios (like needing to stop listening early), you can clean up manually.
Examples
Performance Monitoring
import { request, KeqMiddleware } from 'keq'
function metricsMiddleware(): KeqMiddleware {
return async (context, next) => {
context.emitter.on('fetch:before', ({ context }) => {
context.data.metrics = { fetchStartAt: Date.now() }
})
context.emitter.on('fetch:after', ({ context }) => {
const startTime = context.data.metrics?.fetchStartAt
if (!startTime) return
const duration = Date.now() - startTime
console.log(`Request duration: ${duration}ms`)
// Report performance data
if (duration > 3000) {
console.warn('Request took too long:', context.request.url)
}
// Report metrics to monitoring system
reportMetrics({
url: context.request.url,
method: context.request.method,
duration,
status: context.response?.status
})
})
await next()
}
}
// Apply performance monitoring middleware globally
request.use(metricsMiddleware())
await request.get('/cats')Request Logging
import { request, KeqMiddleware } from 'keq'
function requestLogger(): KeqMiddleware {
return async (context, next) => {
context.emitter.on('fetch:before', ({ context }) => {
console.log(`[${new Date().toISOString()}] ${context.request.method} ${context.request.url}`)
})
context.emitter.on('fetch:after', ({ context }) => {
const status = context.response?.status || 'N/A'
console.log(`[${new Date().toISOString()}] ${context.request.method} ${context.request.url} - ${status}`)
})
await next()
}
}
request.use(requestLogger())
await request.get('/cats')