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.
Related
Disclaimer: I'm not experienced with programming or with networks in general so I might be missing something quite obvious.
So i'm making a function in node.js that should go over an array of image links from my database and check if they're still working. There's thousands of links to check so I can't just fire off several thousand fetch calls at once and wait for results, instead I'm staggering the requests, going 10 by 10 and doing head requests to minimize the bandwidth usage.
I have two issues.
The first one is that after fetching the first 10-20 links quickly, the other requests take quite a bit longer and 9 or 10 out of 10 of them will time out. This might be due to some sort of network mechanism that throttles my requests when there are many being fired at once, but I'm thinking it's likely due to my second issue.
The second issue is that the checking process slows down after a few iterations. Here's an outline of what I'm doing. I'm taking the string array of image links and slicing it 10 by 10 then I check those 10 posts in 10 promises: (ignore the i and j variables, they're there just to track the individual promises and timeouts for loging/debugging)
const partialResult = await Promise.all(postsToCheck.map(async (post, j) => await this.checkPostForBrokenLink(post, i + j)));
within checkPostForBrokenLink I have a race between the fetch and a timeout of 10 seconds because I don't want to have to wait for the connection to time out every time timing out is a problem, I give it 10 seconds and then flag it as having timed out and move on.
const timeoutPromise = index => {
let timeoutRef;
const promise = new Promise<null>((resolve, reject) => {
const start = new Date().getTime();
console.log('===TIMEOUT INIT===' + index);
timeoutRef = setTimeout(() => {
const end = new Date().getTime();
console.log('===TIMEOUT FIRE===' + index, end - start);
resolve(null);
}, 10 * 1000);
});
return { timeoutRef, promise, index };
};
const fetchAndCancelTimeout = timeout => {
return fetch(post.fileUrl, { method: 'HEAD' })
.then(result => {
return result;
})
.finally(() => {
console.log('===CLEAR===' + index); //index is from the parent function
clearTimeout(timeout);
});
};
const timeout = timeoutPromise(index);
const videoTest = await Promise.race([fetchAndCancelTimeout(timeout.timeoutRef), timeout.promise]);
if fetchAndCancelTimeout finishes before timeout.promise does, it will cancel that timeout, but if the timeout finishes first the promise is still "resolving" in the background, despite the code having moved on. I'm guessing this is why my code is slowing down. The later timeouts take 20-30 seconds from being set up to firing, despite being set to 10 seconds. As far as I know, this has to be because the main process is busy and doesn't have time to execute the event queue, though I don't really know what it could be doing except waiting for the promises to resolve.
So the question is, first off, am I doing something stupid here that I shouldn't be doing and that's causing everything to be slow? Secondly, if not, can I somehow manually stop the execution of the fetch promise if the timeout fires first so as not to waste resources on a pointless process? Lastly, is there a better way to check if a large number of links are valid that what I'm doing here?
I found the problem and it wasn't, at least not directly, related to promise buildup. The code shown was for checking video links but, for images, the fetch call was done by a plugin and that plugin was causing the slowdown. When I started using the same code for both videos and images, the process suddenly became orders of magnitude quicker. I didn't think to check the plugin at first because it was supposed to only do a head request and format the results which shouldn't be an issue.
For anyone looking at this trying to find a way to cancel a fetch, #some provided an idea that seems like it might work. Check out https://www.npmjs.com/package/node-fetch#request-cancellation-with-abortsignal
Something you might want to investigate here is the Bluebird Promise library.
There are two functions in particular that I believe could simplify your implementation regarding rate limiting your requests and handling timeouts.
Bluebird Promise.map has a concurrency option (link), which allows you to set the number of concurrent requests and it also has a Promise.timeout function (link) which will return a rejection of the promise if a certain timeout has occurred.
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)
With node.js I want to http.get a number of remote urls in a way that only 10 (or n) runs at a time.
I also want to retry a request if an exception occures locally (m times), but when the status code returns an error (5XX, 4XX, etc) the request counts as valid.
This is really hard for me to wrap my head around.
Problems:
Cannot try-catch http.get as it is async.
Need a way to retry a request on failure.
I need some kind of semaphore that keeps track of the currently active request count.
When all requests finished I want to get the list of all request urls and response status codes in a list which I want to sort/group/manipulate, so I need to wait for all requests to finish.
Seems like for every async problem using promises are recommended, but I end up nesting too many promises and it quickly becomes uncypherable.
There are lots of ways to approach the 10 requests running at a time.
Async Library - Use the async library with the .parallelLimit() method where you can specify the number of requests you want running at one time.
Bluebird Promise Library - Use the Bluebird promise library and the request library to wrap your http.get() into something that can return a promise and then use Promise.map() with a concurrency option set to 10.
Manually coded - Code your requests manually to start up 10 and then each time one completes, start another one.
In all cases, you will have to manually write some retry code and as with all retry code, you will have to very carefully decide which types of errors you retry, how soon you retry them, how much you backoff between retry attempts and when you eventually give up (all things you have not specified).
Other related answers:
How to make millions of parallel http requests from nodejs app?
Million requests, 10 at a time - manually coded example
My preferred method is with Bluebird and promises. Including retry and result collection in order, that could look something like this:
const request = require('request');
const Promise = require('bluebird');
const get = Promise.promisify(request.get);
let remoteUrls = [...]; // large array of URLs
const maxRetryCnt = 3;
const retryDelay = 500;
Promise.map(remoteUrls, function(url) {
let retryCnt = 0;
function run() {
return get(url).then(function(result) {
// do whatever you want with the result here
return result;
}).catch(function(err) {
// decide what your retry strategy is here
// catch all errors here so other URLs continue to execute
if (err is of retry type && retryCnt < maxRetryCnt) {
++retryCnt;
// try again after a short delay
// chain onto previous promise so Promise.map() is still
// respecting our concurrency value
return Promise.delay(retryDelay).then(run);
}
// make value be null if no retries succeeded
return null;
});
}
return run();
}, {concurrency: 10}).then(function(allResults) {
// everything done here and allResults contains results with null for err URLs
});
The simple way is to use async library, it has a .parallelLimit method that does exactly what you need.
Here is a Promise:
var getVersion = new Promise(function(resolve){
getVersionAsyn(resolve);
});
Then a lot of http requests depend on that 'version' and the requests send in the same time.
When page initial, a lot of http requests will send to server and the request are depend on 'version'.
How to prevent getVersion promise run multiple times?
All right. I down vote myself. Such a idiot question.
How to prevent getVersion promise run multiple times?
You are already caching the promise and thus the resolved value of the version so getVersionAsyn() only ever gets called once. You can have as many .then() handlers as you want on a promise and all the subsequent once do is just fetch the saved, resolved value out of the promise data structure. They don't "execute" anything else over and over again.
The way this code works:
var getVersion = new Promise(function(resolve){
getVersionAsyn(resolve);
});
getVersion is a promise and you are already storing it. You can have as many callers as you do this:
getVersion.then(function(version) {
// access the version here
});
And, getVersionAsyn() will only ever be called just once, the first time. From then on, once the promise has resolved, you will just be accessing the saved resolved value (essentially an automatic cache). It is actually a nice design pattern to use promise for caching and async-retrieved value.
Keep the result of the getVersion in the execution context of the subsequent promises...
getVersion.then(version => {
var reqA = doHttp(version, "request A");
var reqB = doHttp(version, "request B");
var reqC = doHttp(version, "request C");
return Promise.all([reqA, reqB, reqC]);
});
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.