fastify-racing
is a plugin which allows you handle possible client request abortions by exposing an AbortSignal
instance that will be aborted just and only when the client has closed the request abruptly (e.g. by closing the browser tab).
On every request and after a first invocation, the plugin well schedule event listeners to the close
event triggered by the Socket instance attached to the request object.
Along with that, the plugin will instanciate and cache an AbortController
instance for each request.
When the close
event is triggered, the plugin will check if the AbortSignal
instance is already aborted, and if not will abort it using the AbortController
instance.
Is guaranteed that one and just one AbortController
and AbortSignal
will be made per request.
If the request was not aborted during its lifetime, the plugin will remove the AbortController
and AbortSignal
from the cache. This by scheduling a hook-handler on the hook onResponse
.
If the request aborted, the same hook will be used for cleaning resources.
A WeakMap
is used under the hood for caching, ensuring that the AbortController
and AbortSignal
instances can be unlinked if not needed anymore, and for instance GC'ed.
Install by running npm install fastify-racing
.
Then register the plugin to your fastify instance:
const fastify = require('fastify')({
logger: true
})
fastify.register(require('fastify-racing'), {
handleError: true,
})
On Setup
handleError
: Indicates to the pluging if an event listener to the Socket error
event should be attached or not. Default true
.
onRequestClosed
: Default callback to be used of none is passed during runtime It will receive as argument the event object similar to the abort
event handler. Default null
There are two ways to use this plugin:
It will return a promise that will be resolved when the request is aborted. It will be resolved with the result of the abort
event object of the AbortSignal
instance. This only if no cb
has been passed as argument.
It supports an object as argument:
opts.handleError
: [Optional] Indicates to the plugin to ignore or listen to the Socket error
event. Default to pluginOption.handleError
passed when registering the pluging or false
.JavaScript
app.get('/', async (req, _reply) => {
const signal = req.race()
const result = await Promise.race([signal, asyncOp(signal)])
if (result.type === 'aborted') return ''
else return `${result}-world`
})
TypeScript
app.post('/', (request: FastifyRequest, reply: FastifyReply) => {
const signal = req.race()
const result: AbortEvent | unknown = await Promise.race([signal, asyncOp(signal)])
if ((<AbortEvent>result).type === 'aborted') return ''
else return `${result}-world`
});
If a callback is provided, no promise will be scheduled/returned during the lifetime of the request.
cb
: Similar signature as onRequestClosed
. Default undefined
or to onRequestClosed
if passed when registering the plugin.JavaScript
app.get('/', (req, reply) => {
const signal = req.race((evt) => {
const result = result.type === 'aborted' ? '' : `${result}-world`
reply.send(result)
})
})
TypeScript
app.post('/', (request: FastifyRequest, reply: FastifyReply) => {
const signal = req.race((evt: AbortEvent) => {
reply.send('')
})
});
interface AbortEvent {
type: 'abort' | string;
reason?: FastifyError | Error
}
interface FastifyRacing {
handleError?: boolean;
onRequestClosed?: (evt: AbortEvent) => void;
}
interface FastifyInstance {
race(cb: FastifyRacing['onRequestClosed']): void
race(opts: Omit<FastifyRacing, 'onRequestClosed'>): Promise<AbortEvent>
race(): Promise<AbortEvent>
}
See test for more examples.