When a pending HTTP request is cancelled by a client/browser it seems that Node with Express continues to process the request. For intensive requests, the CPU is still being kept busy with unnecessary requests.
Is there a way to ask Node.js/Express to kill/stop these pending requests that are requested to be cancelled?
It becomes particularly useful given that since AngularJS 1.5 HTTP request are easily cancellable by calling $cancelRequest()
on $http
/$resource
objects.
Such cancellations could occur when exposing an API method providing results for auto-completion or search fields: when typing in the field to be autocompleted or type-aheaded, previous request(s) can be cancelled.
A global server.timeout
does not solve the problem: 1) it is a priori a global setting for all exposed API methods 2) ongoing processing in the canceled request is not killed.
Injected req
object is shipped with listeners .on()
.
Listening to close
event allows to handle when client close the connection (request cancelled by Angular or, e.g., user closed the querying tab).
Here are 2 simple examples how to use the close
event to stop request processing.
Example 1: Cancellable synchronous block
var clientCancelledRequest = 'clientCancelledRequest';
function cancellableAPIMethodA(req, res, next) {
var cancelRequest = false;
req.on('close', function (err){
cancelRequest = true;
});
var superLargeArray = [/* ... */];
try {
// Long processing loop
superLargeArray.forEach(function (item) {
if (cancelRequest) {
throw {type: clientCancelledRequest};
}
/* Work on item */
});
// Job done before client cancelled the request, send result to client
res.send(/* results */);
} catch (e) {
// Re-throw (or call next(e)) on non-cancellation exception
if (e.type !== clientCancelledRequest) {
throw e;
}
}
// Job done before client cancelled the request, send result to client
res.send(/* results */);
}
Example 2: Cancellable asynchronous block with promises (analog to a reduce)
function cancellableAPIMethodA(req, res, next) {
var cancelRequest = false;
req.on('close', function (err){
cancelRequest = true;
});
var superLargeArray = [/* ... */];
var promise = Q.when();
superLargeArray.forEach(function (item) {
promise = promise.then(function() {
if (cancelRequest) {
throw {type: clientCancelledRequest};
}
/* Work on item */
});
});
promise.then(function() {
// Job done before client cancelled the request, send result to client
res.send(/* results */);
})
.catch(function(err) {
// Re-throw (or call next(err)) on non-cancellation exception
if (err.type !== clientCancelledRequest) {
throw err;
}
})
.done();
}