node js request-promise, retry request on error - javascript

so I have a chain of request that are sent, and once catch at the end, the problem is if I have an error i wanna retry that specific request that caught the error, I know one solution to this would be to add a catch at the end off all the request i send, and when it catches an error it retries that request, but that would lead to too many catch statements, I just want one catch statement at the end that when it catches an error it retrys the specific request
rp.get('https://www.off---white.com/en-us/api/products/' + variant, options2)
.then((data) => {
// doo stuff with request
return rp.post('https://www.off---white.com/en-us/api/bags/' + bagId + '/items', options2)
})
.then((data) => {
// doo stuff with request
})
.catch((error) => {
})

Your example indicates that some requests depend on the response of a previous request. Adding a catch handler at the end of the promise chain would make it extremely difficult to retry the request and continue with subsequent requests. You need to handle the error at the request, not at the end of the promise chain. This is pretty simple to do if you wrap up the request in a helper method.
function request(opts) {
return rp(opts).catch(() => request(opts));
}
request({url: 'https://www.off---white.com/en-us/api/products/' + variant, ...options2})
.then((data) => {
// doo stuff with request
return request({method: 'POST', url: 'https://www.off---white.com/en-us/api/bags/' + bagId + '/items', ...options2});
})
.then((data) => {
// doo stuff with request
})
.catch((error) => {
});
It's not really clear what options2 is and why you use it as the request body in the second request, so this may not work exactly as you would expect, but the parameters passed into request can be tweaked to fit your use case. This will also result in an infinite request loop if the request always fails, you should implement some basic error handling to avoid this infinite loop (e.g., only retry X number of times, or retry only when you get a specific error, etc.)

First of all, you will need to design your rp.get() function in a way such that somehow whenever there is an error, the error object must have an identifier (stored as type key) as of from which request the error is propagated, then on a conditional basis, you can handle the error in a single catch() block accordingly.
const rpWrapper = (type) => { //add other required params
rp.get() //pass required params
.then( (res) => result )
.catch( (err) => { throw { type, err }) }
}
Then, modify your original code to something like,
rpWrapper.get('https://www.off---white.com/en-us/api/products/' + variant, options2)
.then((data) => {
// doo stuff with request
return rpWrapper.post('https://www.off---white.com/en-us/api/bags/' + bagId + '/items', options2)
})
.then((data) => {
// doo stuff with request
})
.catch((error) => {
if(error.type === "request1") { //handle error on first req }
if(error.type === "request2") { //handle error second req }
})

Related

Firebase Admin update function calling multiple callbacks in nodejs

I know for a fact that reading data from firebase with firebase admin returns multiple callbacks. that is why I use ref.once(), like example below:
const ref = db.ref('clients');
ref.once('value', (snapshot) => {
res.send(snapshot.val());
}, (errorObject) => {
console.log('The read failed: ' + errorObject.name);
});
But, when I try to update data I get into the same trouble of receiving multiple callbacks crashing my application, but I can't use once in ref.update, what can I do to prevent receiving multiple callbacks?
app.get('/set-client', (req, res) => {
const ref = db.ref(`users/new_users`)
ref.update({ 'client': uid_client}).then(function(){
console.log("Data saved successfully.");
res.status(200).send("successful")
}).catch(function(error) {
console.log("Data could not be saved." + error);
res.status(201).send("failed")
});
});
Here is a code example.
When interacting with responses in the way you've shown, you might find that using the .then(onFulfilled, onRejected) variant of the then() method may be of use.
Quick note: When using this approach, care must be taken to understand that if the onFulfilled handler throws an exception/rejects, the sibling onRejected handler will not be called with its exception. The exception is instead passed onto the next chained onRejected handler in later then/catch calls. The sibling will only catch exceptions/rejections from steps prior to it in the chain.
Here is an example of the difference:
const somePromise = /* ... */;
const resultPromise = somePromise
.then((data) => { /* handle data */ }) // an error thrown here
.catch((err) => { /* handle error */ }) // gets caught here
// vs.
const resultPromise = somePromise
.then(
(data) => { /* handle data */ }, // an error thrown here, will reject resultPromise
(err) => { /* handle error */ } // instead of being handled here
)
This trait of the onRejected handler in .then(onFulfilled, onRejected) can be applied in a way where headers can't be sent twice for the same response. If for whatever reason the onFulfilled handler throws an exception while trying to send a response, the onRejected handler that is also responsible for sending a response is skipped - preventing any headers already sent errors.
This means that the first code block gets swapped out for:
const ref = db.ref('clients');
ref.once('value')
.then(
(snapshot) => { // got data successfully
console.log('got data successfully');
// don't forget to check snapshot.exists() if data could be missing
res.send(snapshot.val()); // using .json() over .send() is recommended for arbitrary data
},
(error) => { // failed to get data/permission
console.error('Failed to read data at /clients: ', error);
res.status(500).send('Data unavailable.');
}
)
.catch(
(error) => { // if here, either of the above blocks failed - probably due to an error related to the response.
console.error('Failed to send response to client: ', error);
try { res.end() } catch (e) {} // forcefully terminate connection if not already
}
);
and the second code block for:
app.get('/set-client', (req, res) => {
const ref = db.ref(`users/new_users`)
ref.update({ 'client': uid_client }) // uid_client is undefined?
.then(
() => {
console.log("Data updated successfully.");
res.status(200).send("successful");
},
(error) => {
console.error("Data could not be saved.", error);
res.status(500).send("failed"); // don't use HTTP 201 Created here
}
)
.catch(
(error) => { // if here, either of the above blocks failed - probably due to an error related to the response.
console.error('Failed to send response to client: ', error);
try { res.end() } catch (e) {} // forcefully terminate connection if not already
}
);
});
The error handler that logs response errors could be rewritten so that it can be reused by taking in the relevant response object (so it can terminated when needed) and returning the error handler:
const buildResponseErrorHandler = (response) => ((error) => {
console.error('Failed to send response to client: ', error);
try { response.end() } catch (e) {} // forcefully terminate connection if not already
});
// usage:
somePromise
.then(
sendResponseHandlerForSuccess,
sendResponseHandlerForFailure
)
.catch(buildResponseErrorHandler(res)) // buildResponseErrorHandler(res) returns (err) => { /* logs problem */ }

Promise.all() and catching errors

I wrote a snippet of code that gets some a JSON from the Foursquare API. From this JSON, I get IDs of venues. These IDs are then used to get more details from those specific venues by issuing a fetch() request for every ID, and mapping those requests in an array. That array is then passed into Promise.all(). When the API is available, everything works, but it's the error catching that I can't get my head around.
fetch(`https://api.foursquare.com/v2/venues/search?${params}`)
.then(response => response.json())
.then(data => {
const venueIds = data.response.venues.map(venue => venue.id)
const venuePromises = venueIds.map(venueId => {
fetch(`https://api.foursquare.com/v2/venues/${venueId}?${otherParams}`)
.then(response => {
// Must check for response.ok, because
// catch() does not catch 429
if (response.ok) {
console.log('ok')
return response.json()
} else {
Promise.reject('Error when getting venue details')
}
})
})
Promise.all(venuePromises).then(data => {
const venues = data.map(entry => entry.response.venue) // Error for this line
this.parseFsqData(venues)
}).catch((e) => {console.log(e); getBackupData()})
}).catch((e) => {console.log(e); getBackupData()})
function getBackupData() {
console.log('backup')
}
When the API is not available, I get the following console errors (and more of the same):
TypeError: Cannot read property 'response' of undefined
at MapsApp.js:97
at Array.map (<anonymous>)
at MapsApp.js:97
backup
api.foursquare.com/v2/venues/4b7efa2ef964a520c90d30e3?client_id=ANDGBLDVCRISN1JNRWNLLTDNGTBNB2I4SZT4ZQYKPTY3PDNP&client_secret=QNVYZRG0JYJR3G45SP3RTOTQK0SLQSNTDCYXOBWUUYCGKPJX&v=20180323:1 Failed to load resource: the server responded with a status of 429 ()
Uncaught (in promise) Error when getting venue details
I don't understand why then() after Promise.all() is entered, because response is never ok (there is no ok logged in console). Also, I don't understand why the console.log()'s in the catch() blocks aren't executed, or why they are empty. I don't see any caught error information in console, but still the getBackupData function is called. Finally, it is unclear why the last message in console indicates that the error is uncaught, as I expected reject() to make Promise.all() fail.
How can I tactfully catch any errors (included those not normally caught by catch(), such as 429 errors) and call getBackupData when any errors occur?
Your issues are related: namely, the Promise chain must be returned. If you do not return the Promise, you disconnect any of the caller's Promise#catch handling, and any errors in your Promise / then code will result in unhandled promise rejection errors, such as what you have obtained:
Uncaught (in promise) Error when getting venue details
This uncaught promise rejection appears in your code that handles the resolution of fetch:
if (response.ok) {
console.log('ok')
return response.json()
} else {
Promise.reject('Error when getting venue details') // <----
}
Since this code is being used to construct your venuePromises array, its return value will populate venuePromises. If the response was ok, that array element will have the response JSON from return response.json(). If the response failed, there is no return statement that executes, so the array element has the value undefined. Thus, venuePromises would look like this:
[
{ /** some object for successful response */ },
undefined,
{ /** some other object */ },
...
]
Thus when this array is accessed by your Promise.all's success handler, you get the TypeError since you expected all elements of venuePromises to be valid. This TypeError is caught by the Promise.all's .catch handler (which is why it is logged, and you receive the "backup" text in your log).
To fix, you need to return the Promise.reject, and also the Promise.all. Note that there are some cases of implicit return, but I find it nicer to be explicit, especially if the statement spans multiple lines. Since you're returning the Promise.all statement, you can offload its .then and .catch to the caller, resulting in one less nesting level, and one fewer duplicated .catch handler.
fetch(`https://api.foursquare.com/v2/venues/search?${params}`)
.then(response => response.json())
.then(jsonData => {
const venueIds = jsonData.response.venues.map(venue => venue.id);
const venuePromises = venueIds.map(venueId => {
let link = `https://api.foursquare.com/v2/venues/${venueId}?${otherParams}`;
return fetch(link).then(response => {
// Must check for response.ok, because catch() does not catch 429
if (response.ok) {
console.log('ok');
return response.json();
} else {
console.log(`FAILED: ${link}`);
// Return a Promise
return Promise.reject(`Error when getting venue details for '${venueId}'`);
}
});
});
return Promise.all(venuePromises);
})
.then(venueData => {
const venues = venueData.map(entry => entry.response.venue);
this.parseFsqData(venues);
})
.catch(e => {console.log(e); getBackupData()});
function getBackupData() {
console.log('backup')
}
Try returning the rejected promise.
return Promise.reject('Error when getting venue details')
When working with promises you should return inner promises instad of working with inner "thens".
Check this:
fetch(`https://api.foursquare.com/v2/venues/search?${params}`)
.then(response => response.json())
.then(data => {
const venueIds = data.response.venues.map(venue => venue.id);
const venuePromises = venueIds.map(venueId => {
fetch(`https://api.foursquare.com/v2/venues/${venueId}?${otherParams}`)
.then(response => {
// Must check for response.ok, because
// catch() does not catch 429
if (response.ok) {
console.log('ok')
return response.json()
} else {
return Promise.reject('Error when getting venue details')
}
})
});
return Promise.all(venuePromises)
})
.then(venueValues => {
const venues = venueValues.map(entry => entry.response.venue); // Error for this line
this.parseFsqData(venues);
})
.catch((e) => {console.log(e); getBackupData()})
function getBackupData() {
console.log('backup')
}
When returning Promise.all as a value, you're returning a promise so that you can chain further "then" callbacks. The last catch shall capture all promise rejects.
You're also missing the return in the else clause
Hope this helps
I believe the solution is fairly simple; The response of the nested fetch method is missing a return statement. You should get rid of that mysterious error once it is in place.
const venuePromises = venueIds.map(venueId => {
<missing return statement here> fetch(`https://api.foursquare.com/v2/venues/${venueId}?${otherParams}`)
.then(response => {

How to cleanly handle an expected error in fetch?

I need to query some data via a fetch() call but I am never sure whether the request will be successful, HTTP-wise: while the server is up, the URL may (legitimely) hit a non-existing page.
I would like to cleanly handle the case and my current approach is by raising an exception:
// the URL is just an example, I did not have anything CORS-enabled (and unavailable) handy, thus the no-cors mode
fetch(`https://cdnjs.com/libraries/sdfsdfsfsdfsdfsdfdf`, {
mode: 'no-cors'
})
.then(r => {
if (!r.ok) {
console.log("page does not exist")
throw Error();
}
// if the page exists, it will return JSON data
return r.json();
})
.then(r => {
console.log(r)
// things with the JSON happen here
})
.catch(err => null)
I was hoping to just return after Page does not exist, but the (empty) return would then be caught by the next then().
Is this the correct way to exit from a fetch() when the requested URL is not available?
Yes, that looks about right. I would recommend to use functions for your thens.
It makes the fetch more compact and easier to read.
const url = 'some url';
fetch(url)
.then(handleErrors)
.then(parseJSON)
.then(update)
.catch(displayErrors);
function handleErrors(res){
if(!res.ok){
throw Error(`${res.status}: Couldn't load URL.`);
}
return res;
}
function parseJSON (res){
return res.json().then(function(parsedData){
return parsedData.results[0];
})
}
function update (){
//do something with the data
}
function displayErrors(err){
console.log(err);
}

axios.all spread and catch all

I'm using .all method of popular library 'axios' for handling my ajax requests.
But how can I handle errors in case all requests got 404?
for example:
axios.all([
axios.get('http://some_url'),
axios.get('http://another_url'),
])
.then(axios.spread((someUrl, anotherUrl) => {
// ... boring stuff goes there
}))
.catch(() => {
//... error goes there
});
So, seems only one error has ben "catched".
How can I catch them all? Or maybe there any kinda .finally?
The problem (as you already know) is that you will get into catch block as soon as the first promise rejects, making it impossible to collect all failed responses in the same catch. However, you still can handle failed promises manually to aggregate errors and throw afterwards.
Check it this will work for you:
const promises = [
axios.get('http://some_url'),
axios.get('http://another_url'),
]
const promisesResolved = promises.map(promise => promise.catch(error => ({ error })))
function checkFailed (then) {
return function (responses) {
const someFailed = responses.some(response => response.error)
if (someFailed) {
throw responses
}
return then(responses)
}
}
axios.all(promisesResolved)
.then(checkFailed(([someUrl, anotherUrl]) => {
console.log('SUCCESS', someUrl, anotherUrl)
}))
.catch((err) => {
console.log('FAIL', err)
});
You will get into catch block if at least one of the promises fails. You can find one which one by checking err array of responses.
I don't think this is possible due to the fail fast behaviour of Promise.all. If any of your requests fail, they will automatically be the culprit and the result in the catch.
Promise.all([
Promise.reject(Error('1')),
Promise.reject(Error('2')),
Promise.reject(Error('3'))
]).then((results) => {
console.log(results)
}, (error) => {
console.log(error.message)
})
This resulting code will always print 1 as it is the first to fail.I think a similar feature was requested on the repo and they said it wasn't possible.
I was going to leave this as a comment but don't have a high enough reputation yet.
The solution from #dfsq did not work for me because it throws all requests when one has an error. I changed his code so every request either gets resolved or throws an error. #dfsq please review this answer if the code is correct, since I built it on your solution.
const promises = [
axios.get('http://some_url'),
axios.get('http://another_url'),
]
const promisesResolved = promises.map(promise => promise.catch(error => ({ error })))
function checkFailed (then) {
return function (responses) {
responses.forEach(response => {
if (response.error)
throw response;
return then(response);
})
}
}
axios.all(promisesResolved)
.then(checkFailed(response => {
console.log('SUCCESS', response)
}))
.catch((err) => {
console.log('FAIL', err)
});

react native fetch not calling then or catch

I am using fetch to make some API calls in react-native, sometimes randomly the fetch does not fire requests to server and my then or except blocks are not called. This happens randomly, I think there might be a race condition or something similar. After failing requests once like this, the requests to same API never get fired till I reload the app. Any ideas how to trace reason behind this. The code I used is below.
const host = liveBaseHost;
const url = `${host}${route}?observer_id=${user._id}`;
let options = Object.assign({
method: verb
}, params
? {
body: JSON.stringify(params)
}
: null);
options.headers = NimbusApi.headers(user)
return fetch(url, options).then(resp => {
let json = resp.json();
if (resp.ok) {
return json
}
return json.then(err => {
throw err
});
}).then(json => json);
Fetch might be throwing an error and you have not added the catch block. Try this:
return fetch(url, options)
.then((resp) => {
if (resp.ok) {
return resp.json()
.then((responseData) => {
return responseData;
});
}
return resp.json()
.then((error) => {
return Promise.reject(error);
});
})
.catch(err => {/* catch the error here */});
Remember that Promises usually have this format:
promise(params)
.then(resp => { /* This callback is called is promise is resolved */ },
cause => {/* This callback is called if primise is rejected */})
.catch(error => { /* This callback is called if an unmanaged error is thrown */ });
I'm using it in this way because I faced the same problem before.
Let me know if it helps to you.
Wrap your fetch in a try-catch:
let res;
try {
res = fetch();
} catch(err) {
console.error('err.message:', err.message);
}
If you are seeing "network failure error" it is either CORS or the really funny one, but it got me in the past, check that you are not in Airplane Mode.
I got stuck into this too, api call is neither going into then nor into catch. Make sure your phone and development code is connected to same Internet network, That worked out for me.

Categories