I have a service that gets reports:
ReportsResource.getActiveUsers(). This uses $http and then returns a promise.
I then use it like this:
var request = ReportsResource.getActiveUsers();
request.then(populateActiveUsersTable, handleError);
But, the catch is that the request to get the active users report on the backend can take anywhere from a couple of seconds, to 30+ minutes.
If you make a request and no cached report is available, it generates the report, and then the request waits for data (again, could be 2 seconds or 30 minutes) for that request.
If you make a request and the report is currently being generated, it returns a response instantly telling you the report is not ready yet. At which point you can keep polling to see if the report is ready.
If the report is ready (cached), then it returns the response instantly with the report data.
What I need is wrap the request in a timeout that waits up to 10 seconds, and then aborts if the response takes longer than 10 seconds to complete, and starts polling the server to ask if the report is ready yet. But if the request resolves under 10 seconds, it should cancel the timeout and carry out the promise chain as normal.
Not really sure how to handle this one.
Use angular's $q together with $timeout. Create a deferred with $q.defer(), create the request with a timeout, and forward the result in the then handler to resolve your deferred. If the request timeouts, start polling. Return the promise of the deferred immediately.
var d = $q.defer() // defered for the final result
function poll() {
$http({...}).then( //poll request
function(res) {
if (ready(res))
d.resolve(res)
else {
$timeout(poll, 10000)
}
},
function(err) {
d.reject(err)
})
}
$http({ timeout: 10000, ... }).then(
function(res) {
d.resolve(res)
}, // return result directly
function(err) { // error or timeout
if (realError(err)) // check if real error
d.reject(err)
else { //timeout
$timeout(poll, 10000)
}
})
return d.promise
You can reuse the returned promise arbitrarily often invoking then to wait for or obtain the cached result.
Under the situation been given I think it is better to use WebSocket rather than timeout function. With WebSocket, you just need to register the function you need to run every time there's an update/change sent from the server. Instead of keeping polling, websocket require less resource and be more efficiency. But it needs a bit of work on back-end.
The implementation defers for different back-end language. You probably need to talk with the back-end people(or yourself). Hope this can give you some idea.
===Edit===
If you want to use service like timeout or interval, the code below should help:
//inside your controller
var pollingPromise = $q.defer(); //used to controll the fired $http request
var pollingActiveUsers = function() {
ReportsResource.getActiveUsers(pollingPromise).then( function(data){
//stop polling
$interval.cancel(pollingProcess);
//set to the controller's scope
$scope.activeUsers = data;
//populate the activeUsersTable
populateActiveUsersTable();
});
};
//init the first request
pollingActiveUsers();
//polling for every 10secs
var pollingProcess = $interval( function() {
//resolve the previous polling request which mean cancel the previous $http request if it waits longer than 10 secs
pollingPromise.resolve();
//start another polling, a new pollingPromise is required
pollingPromise = $q.defer();
pollingActiveUsers();
}, 10000);
//In your service ReportsResource
//you have to make another change function getActiveUsers() to make this work, you have to pass the pollingPromise to the $http method, so you can cancel the $http request:
var function getActiveUsers = function(promiseObj) {
return $http.get('someUrl', { timeout: promiseObj });
};
Few concerns will be taken:
Already fired $http request should be canceled/resolved if it takes more than 10 secs. so the pollingPromise is the one we need. More info here: cancel unresolved promise
a new $http request should be fired every 10 secs, $interval solve this issue and $interval.cancel() function will stop this interval.
polling should stop immediately when receive the desired data
The code might need to be altered when you apply in your app.
Related
So I've got a React App that creates a video, this is a very long api request taking between 1 and 10 minutes to resolve. I have a separate api call which I need to run continually every couple of seconds to check the status until the first promise is resolved (and the video is compiled).
const promise1 = axios.post("/api/create", data);
//promise1 takes between 1 and 10 minutes to resolve (video creation).
const promise2 = axios.get(`/progress-${uniqueId}.txt`);
// I need promise2 (which checks status of promise1) to continually run
//at an interval (every 5 seconds?) until promise 1 resolves
Promise.race([promise1, promise2]).then(res=>{
//this obviously returns promise2 first, as expected, but
//once it comes back I need it to refire after 5 seconds
//continually until promise 1 resolves
console.log(res)});
};
Any idea how I can recursively call Promise2 until Promise1 Resolves?
Promises, by definition, are functions which return a value at most once, at a later point in time. You can't re-run a promise, the best you can do is recreate one using some factory pattern.
Alongside that, you need a mechanism to check if your create promise has been fulfilled.
// Send create request
const creationPromise = axios.post("/api/create", data);
// Track creationPromise state
let isCreated = false;
creationPromise.then(() => isCreated = true);
// factory for creating a new progress request on-demand
const progressFactory = () => axios.get(`/progress-${uniqueId}.txt`);
// While the created request hasn't completed, loop
while (!isCreated) {
// Send new progress request
const progress = await progressFactory();
console.log("progress", progress);
}
// Done, create returned
console.log("Finished!");
Well I have another approach. How about if instead of hanging there for up to ten minutes, send whatever is needed to the backend as soon as you get it send back a status of 202 = The HyperText Transfer Protocol (HTTP) 202 Accepted response status code indicates that the request has been accepted for processing, but the processing has not been completed; in fact, processing may not have started yet. The request might or might not eventually be acted upon, as it might be disallowed when processing actually takes place. you don’t have to send the response at the end you can do it at any time by doing so you release the client while the server keeps processing.
My requirement is like this
I want to run an axios call.
I don't want to block the code until it finished.
Also I don't want to know it's 200 or 500
This is my experiment code.
function axios() {
console.log("axios calling...");
return new Promise((resolve, reject) => {
setTimeout(function() {
resolve(console.log("DONE!"));
}, 10000);
});
}
function run() {
axios();
console.log("Before");
return;
console.log("This should never log");
}
run();
According to this experiment I think even though I return from the function still that promisified function is run. Which means it guaranteed call the axios.
My concern is,
if axios take 10 mins established connection with the API (NOT SEND THE POST REQUEST) if I return from the next line will axios wait that 10 mins and send the request or break the connection establishing when I return?
A promise will wait for data regardless of weather you call .then() on it or not (note that there are some exceptions[1] ). And the page will continue processing and wait for events as long as it's opened.
So if the request takes 10 minutes it will continue waiting (barring timeouts on either the server, gateway, router or browser) until you close the page/tab.
[1] Some libraries only triggers promise creation when you call .then() for example knex will keep returning a query object instead of a promise until you call .then() on the query object)
As documented in the angular docs,
timeout – {number|Promise} – timeout in milliseconds, or promise that should abort the request when resolved.
Right now I am setting timeout to promise, so I can manually cancel the request by promise.resolve().
Right now, I also want to make it capable to config timeout value, instead of having the request timeout being 120 seconds.
How can I configure it without affecting the existing cancel request functionality?
You could do somthing like this
$scope.qPromiseCall = function()
{
var timeoutPromise = $timeout(function()
{
//aborts the request when timed out
canceler.resolve();
console.log("Timed out");
}, 250);
//we set a timeout for 250ms and store the promise in order to be cancelled later if the data does not arrive within 250ms
var canceler = $q.defer();
$http.get("data.js", {timeout: canceler.promise} )
.success(function(data)
{
console.log(data);
$timeout.cancel(timeoutPromise);
//cancel the timer when we get a response within 250ms
});
}
For more details look at
Setting a timeout handler on a promise in angularjs
First Answer by #Khanh TO
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();
}
});
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);
}