Is it possible, in node.js, to make an asynchronous call that times out if it takes too long (or doesn't complete) and triggers a default callback?
The details:
I have a node.js server that receives a request and then makes multiple requests asynchronously behind the scenes, before responding. The basic issue is covered by an existing question, but some of these calls are considered 'nice to have'. What I mean is that if we get the response back, then it enhances the response to the client, but if they take too long to respond it is better to respond to the client in a timely manner than with those responses.
At the same time this approach would allow to protect against services that simply aren't completing or failing, while allowing the main thread of operation to respond.
You can think of this in the same way as a Google search that has one core set of results, but provides extra responses based on other behind the scenes queries.
If its simple just use setTimout
app.get('/', function (req, res) {
var result = {};
// populate object
http.get('http://www.google.com/index.html', (res) => {
result.property = response;
return res.send(result);
});
// if we havent returned within a second, return without data
setTimeout(function(){
return res.send(result);
}, 1000);
});
Edit: as mentioned by peteb i forgot to check to see if we already sent. This can be accomplished by using res.headerSent or by maintaining a 'sent' value yourself. I also noticed res variable was being reassigned
app.get('/', function (req, res) {
var result = {};
// populate object
http.get('http://www.google.com/index.html', (httpResponse) => {
result.property = httpResponse;
if(!res.headersSent){
res.send(result);
}
});
// if we havent returned within a second, return without data
setTimeout(function(){
if(!res.headersSent){
res.send(result);
}
}, 1000);
});
Check this example of timeout callback https://github.com/jakubknejzlik/node-timeout-callback/blob/master/index.js
You could modify it to do action if time's out or just simply catch error.
You can try using a timeout. For example using the setTimeout() method:
Setup a timeout handler: var timeOutX = setTimeout(function…
Set that variable to null: timeOutX = NULL (to indicate that the timeout has been fired)
Then execute your callback function with one argument (error handling): callback({error:'The async request timed out'});
You add the time for your timeout function, for example 3 seconds
Something like this:
var timeoutX = setTimeout(function() {
timeOutX = null;
yourCallbackFunction({error:'The async request timed out'});
}, 3000);
With that set, you can then call your async function and you put a timeout check to make sure that your timeout handler didn’t fire yet.
Finally, before you run your callback function, you must clear that scheduled timeout handler using the clearTimeout() method.
Something like this:
yourAsyncFunction(yourArguments, function() {
if (timeOutX) {
clearTimeout(timeOutX);
yourCallbackFunction();
}
});
Related
I have this route:
app.get("/api/current_user", (req, res) => {
//This function takes 3~ seconds to finish
someObj.logOn(data => {
someObj.setData(data);
});
//This will return before function finishes
return res.send(someObj.data);
});
Here is the .logOn() function (simplified):
logOn(_callback) {
//has some data
var info = {};
//returns data in callback
_callback(info);
}
Question: Is there some way to to wait for the function to finish before returning? This function does not deal with promises, so I cannot use async/await. I couldnt find any good answers, and anything with waiting either had to deal with promises or setTimeout. Both of which would not work, right?
Note: If I put the return statement inside the callback right under someObj.setData(data); I will get an error like this:
can't set headers after they are sent
This error occurs not on the intial route load, but after I refresh one more time.
Use a callback. Changing res.send to res.end so that no headers are set. It seems that something is written to the response in the functions we can't see.
app.get("/api/current_user", (req, res) => {
//This function takes 3~ seconds to finish
someObj.logOn(data => {
someObj.setData(data);
res.end(JSON.stringify(data));
});
});
I have a Node server that uses Connect to insert some middleware which attempt to transform a response stream from node-http-proxy. Occasionally this transformation can be quite slow and it would be preferable in such cases to simply return a response that doesn't include the transformations or alternatively includes their partial application.
In my application I've attempted to use setTimeout to call next after some number of milliseconds in the context of the transformation middleware. This generally works but exposes a race condition where if the middleware has already called next and then setTimeout fires and does the same an error occurs that looks like: Error: Can't set headers after they are sent.
Eventually I evolved the setTimeout to invoke next with an Error instance as its first argument and then later on in my middleware chain would catch that error and assuming res.headersSent was false would start sending the response via res.end.call(res). This worked and surprisingly I could set the timeout to nearly nothing and the response would happen significantly faster and be complete.
I feel like this last method is a bit of a hack and not immune from the same race condition, but perhaps appears to be a little more resilient. So I would like to know what sort of idiomatic approaches Node and Connect have for dealing with this kind of thing.
How can I go about timing out slow middleware and simply return the response stream?
Currently this seems to do what I want, more or less, but again feels a bit gross.
let resTimedout = false;
const timeout = setTimeout(() => {
if (!resTimedout) {
resTimedout = true;
next();
}
}, 100);
getSelectors(headers, uri, (selectors) => {
const resSelectors = Object.keys(selectors).map((selector) => {
...
};
const rewrite = resRewrite(resSelectors);
rewrite(req, res, () => {
if (!resTimedout) {
resTimedout = true;
clearTimeout(timeout);
next();
}
});
});
setTimeout returns the id of the timeout, so you can then run clearTimeout passing in the id. So when the transformation is complete just clear the timeout before you call next.
var a = setTimeout(()=>{}, 3000);
clearTimeout(a);
https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout
Use async.timeout or Promise.timeout from Bluebird or Q library
You could eliminate the need for global variables and decide this per request:
const rewrite = resRewrite(resSelectors);
rewrite(req, res, () => {
// set a timer to fail the function early
let timer = setTimeout(() => {
timer = null;
next();
}, 100);
// do the slow response transformation
transformResponse((err, data) => { // eg. callback handler
clearTimeout(timer);
if (timer) next();
});
});
How it works
If the timer ends first, it sets itself to null and calls next(). When the transform function ends, it will see the timeout is null and not call next().
If the response transform is faster, it clears the timeout to prevent it running next later on.
Consider the following:
A web application that can have up to 100 concurrent requests per second
Each incoming request currently makes a http request to an endpoint to get some data (which could take up to 5 seconds)
I want to only make the http request once, i.e. I don't want to make concurrent calls to the same endpoint as it will return the same data
The idea is only the first request will make the http call to get the data
While this call is 'inflight', and subsequent requests will not make the same call and instead 'wait' for the first inflight request to complete.
When the initial http request for data has responded, it must respond to all calls with the data.
I am using Bluebird promises to for the async function that performs the http request.
I would like to create/use some sort of generic method/class that wraps the business logic promise method. This generic method/call will know when to invoke the actual business logic function, when to wait for inflight to finish and then resolve all waiting calls when it has a response.
I'm hoping there is already a node module that can do this, but can't think of what this type of utility would be called.
Something similar to lodash throttle/debounce, but not quite the same thing.
I could write it myself if it doesn't exists, but struggling to come up with a sensible name for this.
Any help would be appreciated.
You can implement a PromiseCaching, like:
module.exports = function request(url) {
if (caches[url]) return caches[url];
var promise = req(url);
return (caches[url] = promise);
};
var req = require('');
var caches = {};
EDIT:
Let me be more explanatory:
Here is not about caching of the responses, but about caching of promises. Nodejs is single threaded, that means, there no concurrent function calls, even when everything is async, at one point of time, runs only one peace of code. That means, there will be somebody first calling the function with the url y.com/foo, there will be no promise in the cache, so it will fire the GET request und will cache and return that promise. When somebody immediately calls the function with the same url, no more requests are fired, but instead the very first promise for this url will be returned, and the consumer can subscribe on done/fail callbacks.
When the response is ready and the promise is fulfilled, and somebody makes the request with the same url, then again, it will get the cached promise back, which is already ready.
Promise caching is a good technique to prevent duplicate async tasks.
A web application can only have 6 concurrent requests because that's the hard browser limitation. Older IE can only do 2. So no matter what you do - this is a hard limit.
In general, you should solve the multiplexing on the server side.
Now - to your actual question - the sort of caching you're asking for is incredibly simple to do with promises.
function once(fn) {
var val = null; // cache starts as empty
return () => val || (val = fn()); // return value or set it.
}
var get = once(getData);
get();
get(); // same call, even though the value didn't change.
Now, you might want to add an expiry policy:
function once(fn, timeout) {
var val = null, timer = null; // cache starts as empty
return () => val || (val = fn().tap(invalidate)); // return value and invalidate
function invalidate() {
clearTimeout(timer); // remove timer.
timer = setTimeout(() => val = null, timeout);
}
}
var get = once(getData, 10000);
You might also want to uncache the result if it fails:
function once(fn, timeout) {
var val = null, timer = null; // cache starts as empty
return () => val ||
(val = fn().catch(e => value = null, Promise.reject(e)).tap(invalidate));
function invalidate() {
clearTimeout(timer); // remove timer.
timer = setTimeout(() => val = null, timeout);
}
}
Since the original functionality is one line of code, there isn't a helper for it.
You can used promise for prevent duplicate request same time
Example write in nodejs, you can using this pattern in browser as well
const rp = require('request-promise'),
var wait = null;
function getUser(req, rep, next){
function userSuccess(){
wait = null;
};
function userErr(){
wait = null;
};
if (wait){
console.log('a wait');
}
else{
wait = rp.get({ url: config.API_FLIX + "/menu"});
}
wait.then(userSuccess).catch(userErr);
}
I'm building a very simple client/server app using node.js and redis for the first time. After successfully firing up my redis client and my http server, I'm trying to do a simple SET/GET with my redis client.
I first do:
client.set('apple', 10, redis.print);
which returns a Reply:Ok.
Immediately after, I execute:
client.get('apple', function(err, reply) {
count = parseInt(reply);
return count;
});
Strangely, upon printing out count, I get undefined. But, if I use redis.print, such as:
client.get('apple', redis.print);
A Reply: 10 is returned in the console. 10 also shows up if I do a console.log(count).
I thought maybe I was misusing the set functions in the module, but after referring to these both, I am unsure of what's causing my errors:
node_redis - github readme
redis.io API - GET
upon printing out count, I get undefined.
client.get('apple', function(err, reply) {
count = parseInt(reply);
return count;
});
console.log(count); // is this where you get undefined?
// count at this point is, in fact, undefined,
// because it's set in a callback which was not yet fired.
I don't program in node.js, so I'm not sure about benefits of returning count from the callback. Where would it return it to? Isn't it supposed to be something like this?
client.get('apple', function(err, reply) {
count = parseInt(reply);
// do something with the count here. Print it or whatever.
});
Or you could trap it in a closure, but then you'll need to somehow wait for callback to return and set the variable. How it can be done - I have no idea.
var count = null;
client.get('apple', function(err, reply) {
count = parseInt(reply);
});
// TODO: wait for callback
console.log(count);
This is asynchonous, meaning the rest of the code is executed while waiting for the callback.
if you want to check if your set worked just print it :
client.get('apple', function(err, reply) {
console.log(reply);
});
I am writing a mock for the node-redis module and using Jasmine to test it. I write tests for various aspects of Redis commands and my intention is to be able to run the tests against the original Redis module as well.
My problem is: if I understood node-redis correctly, the returned value of the asynchronous functions of node-redis is different depending upon the command was sent to Redis or queued for sending later (for example, to be sent after the connection is complete). But I would like to test the returned value, too, and if I write a test such as the one below:
it("should update value", function () {
var client = redis.createClient();
client.set("key", "1st");
var value = client.get("key", function (err, data) {
expect(err).toBeNull();
expect(data).toBe("1st");
});
expect(value).toBe(true);
});
it will not pass if I use the real Redis module, because there will not be enough time to connect to database.
Is there a way to wait the asynchronous request to be executed to go ahead testing the code?
(Answers with different approaches to this problem are welcomed too.)
expect(err).toBeNull() == expect(value).toBe(true); because asynchronous functions return nothing (undefined). You should do this only
client.set("key", "1st");
client.get("key", function (err, data) {
expect(err).toBeNull();
expect(data).toBe("1st");
});
But if you want to wait you should use something like this:
var value = undefined;
client.get("key", function (err, data) {
expect(err).toBeNull();
expect(data).toBe("1st");
value = true;
});
setInterval(function(){ expect(value).toBe(true); }, 10000); // wait 10 seconds for connection