Express.js: Do background task after sending response to client - javascript

It is a simple controller. Receive requests from users, do task and response to them. My goal is to make the response as soon as possible and do the task then so the user will not notice the lengthy waiting time.
Here is my code:
router.post("/some-task", (req, res, next) => {
res.send("ok!");
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5000); // lengthy task
});
When post to this controller, I need to wait 5000ms before getting the response, which is not I want. I have tried promise but don't work either. What is the correct way to do it?

Improve version for Evyatar Meged answer:
router.post("/some-task", (req, res, next) => {
res.send("ok!");
setTimeout(()=>{
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5000); // lengthy task
}, 0);
});
It is not about res.end, just setTimeout do the thing. But I don't recommend this since it will block the entire app for handling other requests. You may need to think to use child process instead.

You can have a callback after res.end that will execute as soon as the response was sent.
router.get('/', function(req, res, next) {
res.end('123', () => {
setTimeout(() => {console.log('456')}, 5000)
})
});
Notice you're browser will receive 123 and after 5 seconds your server will log 456.

Related

How to send response asynchronously in nodejs / express

const express = require("express");
const router = express.Router();
router.get("/", async (req, res, next) => {
setTimeout(() => {
res.send("Hello Stackoverflow");
}, 10000);
});
module.exports = router;
When i send a get request from two browser tabs at same time first request gets completed in 10 seconds and second one takes 20 seconds.
I expected both the requests to get completed in 10 seconds as i used setTimeout to make it async.
Am i doing something wrong here or nodejs does't take another request before completing the first one ?
What i want this code to do is :
request ──> setTimeout
request ──> setTimeout
setTimeout completes ──> send response
setTimeout completes ──> send response
what it currently doing is :
request ──> setTimeout
setTimeout completes ──> send response
request ──> setTimeout
setTimeout completes ──> send response
How can i achieve this ?
EDIT =>
I suspect this is some chrome only behaviour as the code works in firefox. Also if i open one normal tab and another incognito tab it works as expected.
This is caused by your browser wanting to reuse the connection to the Express server for multiple requests because by default, Express (actually, Node.js's http server does this) is telling it to by setting the header Connection: keep-alive (see below for more information).
To be able to reuse the connection, it'll have to wait for a request to finish before it can send another request over the same connection.
In your case, the first request takes 10 seconds, and only then is the second request sent.
To illustrate this behaviour, try this:
router.get("/", async (req, res, next) => {
res.set('connection', 'close');
setTimeout(() => {
res.send("Hello Stackoverflow");
}, 10000);
});
This tells the browser that the connection should be closed after each request, which means that it will create a new connection for the second request instead of waiting for the first request to finish.
For more information, look here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection

node.js express: Is there a way to cancel http response in middleware?

I am writing a timeout middleware with express in node.js.
app.use((req, res, next) => {
res.setTimeout(3000, () => {
console.warn("Timeout - response end with 408")
res.status(408).json({ "error": "timeout 408" });
// !!! error will happen with next function when call like `res.send()`:
// Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
next()
})
If there's an endpoint that takes more than 3000 ms, my middleware will repsond with 408. However, the next function will respond again. I don't want to check if the response has been already sent by res.headersSent api every time.
Is there a better way to handle this - like the title said - to cancel the next response in the middleware?
It's your own code in the response handler that is still running (probably waiting for some asynchronous operation to complete). There is no way to tell the interpreter to stop running that code from outside that code. Javascript does not have that feature unless you put that code in a WorkerThread or a separate process (in which case you could kill that thread/process).
If you're just trying to suppress that warning when the code eventually tries to send its response (after the timeout response has already been sent), you could do something like this:
app.use((req, res, next) => {
res.setTimeout(3000, () => {
console.warn("Timeout - response end with 408")
res.status(408).json({ "error": "timeout 408" });
// to avoid warnings after a timeout sent,
// replace the send functions with no-ops
// for the rest of this particular response object's lifetime
res.json = res.send = res.sendFile = res.jsonP = res.end = res.sendStatus = function() {
return this;
}
});
next();
});

Express timeout if endpoint is taking too long

I am trying to timeout a response back to the client if its taking too long on my server. But any solution I have found does not seem to trigger the timeout. This should timeout after 2 seconds, but for some reason my endpoint resolves with no timeout happening. Am I doing this correct?
app.use((req, res, next) => {
res.setTimeout(2000, () => {
console.log('Request has timed out.');
res.status(503).send('Service unavailable. Please retry.');
});
next();
});
// endpoints
...
// resolves after 5 seconds
app.get('/timeoutTest', (req, res) => {
const time = new Date().valueOf();
const futureTime = time + 5000;
while (new Date().valueOf() < futureTime) {
// nothing to do but wait
}
return res.status(200).send('5 seconds have passed since call');
});
res.setTimout does not seem to be triggering if it has the next() at the bottom of the middleware. However. If I comment out next() it does trigger my res.setTimeout after 2 seconds, but of course any http requests coming in wont get reached because there is no next()
There is an express module that will do this already: connect-timeout
Be aware - Node will continue to process your request, so this will not solve CPU usage issues etc.

Non-block setTimeout for web server

I have a web server that upon request makes a phone call, waits 3 seconds and then checks if that phone call is still ongoing. I used setTimeout to integrate this but this blocks all other connections to the web server until the timeout has finished.
// example get request
app.get("/", function(req, res) {
// take an action
example.makeCall(function() {
setTimeout(function() {
// check the action
example.checkCall(function() {
res.status(200)
})
}, 3000)
})
})
Is there some other way of adding a timeout to a request without blocking all other incoming requests?
Not sure why your additional API requests are being blocked, new API requests should use a new invocation of your route's callback function on the call stack and shouldn't be dependant on previous callback functions finishing to be added to the call stack.
The issue might not be that the setTimeout is blocking and may be another problem such as your phone call API blocking new calls being made before a previous call has finished.
But something to try could be to wait 3 seconds in a promise to try to get around potential blocking.
function waitThree() {
return new Promise(function(resolve) {
setTimeout(function() {
resolve();
}, 3000);
});
}
app.get("/", function(req, res) {
// take an action
example.makeCall(function() {
waitThree.then(function() {
example.checkCall(function() {
res.status(200)
});
});
})
});

Stop the execution of a cancelled request in Express

I am creating an application which makes a lot of HTTP requests to another server and it may take up to 1 minute to complete one of such requests. Some of the users cancel the request, however my app still executes the cancelled requests.
Here is my code :
var app = express();
app.get('/something', function (req, res) {
function timeout(i) {
setTimeout(function () {
// lets assume there is a http request.
console.log(i);
timeout(++i);
}, 100);
}
req.connection.on('close', function () {
console.log('I should do something');
});
timeout(1);
});
app.listen(5000);
Basically, What I want is stop console.log(i) calls after client closes connection. Also, If possible the client omit "close-{id}" event and when backend recevies close-{id} event, it terminates {id} request.
Note : I used setTimeout to show callback mechanizim. It is not the real code.
Thank you for your help.
From the documentation, "http.request() returns an instance of the http.ClientRequest class." You could call the abort() method of the returned object. (Warning, untested code).
var http = require('http'); // This needs to go at the top of the file.
app.get('/something', function (req, res) {
var remote_request = http.request("www.something.com", function(data){
res.writeHeader(200, {"Content-type": "text/plain"});
res.end(data);
});
req.on("close", function() {
remote_request.abort();
});
});
Assign your setTimeout to a var, and then use clearTimeout in the close handler. If might take some clever restructuring based on your method structure but something like:
var myTimeout = setTimeout(...)
...
req.connection.on('close', function() {
clearTimeout(myTimeout);
});

Categories

Resources