Node HTTP Server Request Abort Propagation
The AbortController
has become a standard for canceling asynchronous operations in javascript. Its utility extends beyond fetch
, with native support in Node.js > 15 as well as core modules like child_process
and growing adoption in community libraries including discussion in popular ORMs (drizzle
, knex
, prisma
, sequelize
). In addition to being used for cancelling async operations, the abort signal produced from an AbortController can also be leveraged to cancel synchronous CPU-bound tasks by occasionally checking for abort.
Practical Example
Client-Side Request Abortion
const ac = new AbortController();
setTimeout(ac.abort.bind(ac), 675);
fetch('http://localhost:1337', {
signal: ac.signal,
})
.then((res) => {
if (res.status !== 200) throw new Error(`non-200 status ${res.status}`);
return res.json();
})
.then(console.log)
.catch((error) => {
if (error.name === 'AbortError') {
console.log('Request aborted by the client.');
} else {
console.error(error);
}
});
In the client-side script, an AbortController
is employed to signal request abortion after a specified timeout. The client initiates a fetch request to a server, passing the associated signal. If the client decides to abort the request prematurely, the server handles this interruption gracefully.
Server-Side Propagation
const http = require('node:http');
const fetch = require('node-fetch');
const app = require('connect')();
app.use((req, res, next) => {
const ac = new AbortController();
req.abortSignal = ac.signal;
res.on('close', () => {
console.log('Request aborted by the client.');
ac.abort();
});
next();
});
app.use('/', async (req, res) => {
for (let i = 0; i < 10; i++) {
console.log('working...');
await new Promise((r) => setTimeout(r, 50));
if (req.abortSignal.aborted) return res.end();
}
let users = [];
try {
const usersRes = await fetch(
'https://jsonplaceholder.typicode.com/users',
{
signal: req.abortSignal,
}
);
users = await usersRes.json();
} catch (error) {
res.statusCode = 500;
return res.end();
}
res.write(JSON.stringify(users));
res.end();
});
http.createServer(app).listen(1337, () => {
console.log('Server is listening on port 1337');
});
In the server-side script, the AbortController
is integrated into the request-response lifecycle. Each incoming request creates a new AbortController
, and its associated signal is stored in the request object. The server listens for the response’s close
event, triggering the AbortController
’s abort method to signal the abortion through the call stack.
Conclusion
By incorporating the AbortController
for server-side request handling, Javascript developers can create more responsive and resource-efficient applications. The included client-server examples illustrate the implementation of client-side request abortion and seamless propagation of the abort signal through the server’s call stack. This integration ensures your applications stay efficient and deliver an optimal user experience.