javascript promise handling, fail to handle error - javascript

I'm having some trouble understanding what I'm doing wrong. I have a function that receives a url to which should make a GET request, in case of success should fill a combo with the received data (this depends which function calls it), in case of fail it should execute some common code.
getFirstCombo = () => {
this.getFromApi('/First/GetAll')
.then(data => this.setState({firstComboOptions: this.parseCombo(data)}))
.catch(error => console.log('ERROR2: ', error));
}
getSecondCombo = () => {
this.getFromApi('/Second/GetAll')
.then(data => this.setState({secondComboOptions: this.parseCombo(data)}))
.catch(error => console.log('ERROR2: ', error));
}
parseCombo = (data: any) => {
const combo = data.map(item => (
{ label: item.description, value: item.id }
));
return combo;
}
getFromApi = (url: string) : Promise<any> => {
return restApiAxios.get(url)
.then(response => {
return response.data;
})
.catch(error => {
console.log('ERROR: ', error);
});
}
this code is executed on the componentDidMount of the react component, but when it fails, it first prints :
ERROR: Error: Network Error
at createError (createError.js:16)
at XMLHttpRequest.handleError (xhr.js:83)
and immediately after:
PanelDatos.tsx:50 ERROR2: TypeError: Cannot read property 'map' of undefined
at PanelDatos.parseCombo (PanelDatos.tsx:55)
at PanelDatos.tsx:50
so, when failing executes the catch block from getFromApi and then it tries to execute the then block in getFirstCombo, which triggers the catch block from the same function cause data does not exist, why is that? shouldnt it just execute the first catch?
thanks in advance

.catch returns a promise much like .then, allowing you to return a custom value and handle it that way.
Try doing the following to observe the effect:
Promise
.reject(1)
.catch(e => e) // Catch the error and return it
.then(console.log) // will log 1 to the console
This means you'll need to add some checks if you want to continue to use promises like this:
Promise
.reject(new Error('haha'))
.catch(err => ({err}))
.then(({err, data}) => {
if(err) return // Do nothing
// enter code here
})
However, using async / await will improve readability even more:
getFirstCombo = async () => {
let response
try {
response = await this.getFromApi('/First/GetAll')
} catch (e) {
return // Exit early
}
let parsed
try {
parsed = this.parseCombo(data)
} catch (e) {
console.log(e)
return // Exit early
}
return this.setState({firstComboOptions: parsed})
}
And, of course, throw the error again in your catch block in your api to allow it to handle api calls.

This is happening since inside getFromApi catch method on the error you are not returning anything, so by default, it is returning a resolved promise with null response and the execution goes inside getFirstCombo then method, causing another error. You can update your code to resolve this like:
getFromApi = (url: string): Promise<any> => {
return restApiAxios.get(url)
.then(response => response.data)
.catch(error => Promise.reject(error));
}
The static Promise.reject function returns a Promise that is rejected. So, it will go directly into catch of wherever getFromApi is called.
DEMO:
async function getFromApi(url) {
return fetch(url) // rejects
.then(response => response.json())
.catch(err => Promise.reject(err))
}
async function getFirstCombo() {
getFromApi('https://no-such-server.abcd')
.then(data => console.log('data: ', data))
.catch(error => console.log('ERROR2: ', error));
}
getFirstCombo()
DEMO #2 (With getFirstCombo function not having any catch block) :
async function getFromApi(url) {
return fetch(url) // rejects
.then(response => response.json())
.catch(err => {
console.log('ERROR in getFromApi(): ', err);
return null; // return null, empty array, 0 or false... as per your requirement
})
}
async function getFirstCombo() {
getFromApi('https://no-such-server.abcd')
.then(data => console.log('data: ', data))
// Same value set in catch block of getFromApi will return in this then() block
// Validate this `data` variable before processing it further like:
// if(data === null) this means an error had occurred
// else continue with your logic
}
getFirstCombo()

Related

Why is `.then()` processed when fetch fails?

Consider the following code where I tried to shield fetch against any unsuccessful connections (I call them non "200-ish" in the comments) and provide a function that will make use of its successful results:
const callApi = () => {
return fetch("http://doesnotexist.example.com")
.then((r) => {
// check for non200-ish respnses (404, etc.)
if (!r.ok) {
console.log(`status for failed call: ${r.status}`);
throw new Error(`${r.statusText} (${r.status})`);
} else {
// continue the chain because the result is 200-ish
return r;
}
})
.then((r) => r.json())
.catch((err) => {
// should catch network errors (DNS, etc.) as well as replies that are not 200-ish
console.log(`call failed: ${err}`);
});
};
callApi().then((r) => console.log("the call was successful"));
The result is
call failed: TypeError: Failed to fetch
the call was successful
Since this is a network issue, the first then() was not executed and we jumped directly to the catch(). But why has the last then() been executed?
The next example is for a call that returns an error code:
const callApi = () => {
return fetch("https://httpstat.us/500")
.then((r) => {
// check for non200-ish respnses (404, etc.)
if (!r.ok) {
console.log(`status for failed call: ${r.status}`);
throw new Error(`${r.statusText} (${r.status})`);
} else {
// continue the chain because the result is 200-ish
return r;
}
})
.then((r) => r.json())
.catch((err) => {
// should catch network errors (DNS, etc.) as well as replies that are not 200-ish
console.log(`call failed: ${err}`);
});
};
callApi().then((r) => console.log("the call was successful"));
The output is
status for failed call: 500
call failed: Error: Internal Server Error (500)
the call was successful
Same question as above.
Finally, for 200 everything is fine:
const callApi = () => {
return fetch("https://httpstat.us/200")
.then((r) => {
// check for non200-ish respnses (404, etc.)
if (!r.ok) {
console.log(`status for failed call: ${r.status}`);
throw new Error(`${r.statusText} (${r.status})`);
} else {
// continue the chain because the result is 200-ish
return r;
}
})
.catch((err) => {
// should catch network errors (DNS, etc.) as well as replies that are not 200-ish
console.log(`call failed: ${err}`);
});
};
callApi().then((r) => console.log("the call was successful"));
Another way to address the question would be: how to stop processing at the catch()?
You're returning the result of a fetch().then().catch() chain, and calling a .then() on that:
callApi().then((r) => console.log("the call was successful"));
That last .then() will always be executed, because the promise was handled successfully. It either:
Completed successfully, or
catch took care of any errors that occurred`

Promise.reject in .then() returning undefined

I've currently got an ES6 class with a constructor and two methods. I'm a tad confused as to why using Promise.reject(ex) within the .then() is resolving undefined. If someone wouldn't mind explaining what I'm doing wrong that would be much appreciated.
I have a method called getYaml() which contains the following:
_getYaml(recordId) {
return new Promise((resolve, reject) => {
fs.readFile(this.workingDir + '/' + recordId + '.yaml', 'utf8', function(err, data) {
if (err) reject(err)
resolve(data)
})
})
}
I then have another method called getCompDoc which makes use of the other method like so:
getCompDoc(recordId) {
return this._getYaml(recordId).then(data => {
let yaml = data
let yamlObj
try {
yamlObj = YAML.safeLoad(yaml)
} catch (ex) {
ex.message = `Failure to parse yaml. Error: ${ex.message}`
logger.error(ex.message, {}, ex)
return Promise.reject(ex)
}
let compDoc = {
// ...
}
return compDoc
}).catch(err => {
logger.error(err, {}, err)
})
}
I then have a test to check that the YAML parsing error is caught and then a promise rejected which looks like so:
describe('error cases', () => {
const fakeRecordId = 'SomeYaml'
beforeEach(() => {
sinon.stub(myClass, '_getYaml').returns(Promise.resolve('{{&^%}egrinv&alidgj%^%^&$£#£#£}'))
})
afterEach(() => {
myClass._getYaml.restore()
})
it('Error parsing yaml, rejects with error', () => {
return expect(myClass.getCompDoc(fakeRecordId)).to.be.rejected.then(response => {
expect(response.message).to.match(/Failure to parse yaml. Error: /)
})
})
})
Test output:
AssertionError: expected promise to be rejected but it was fulfilled with undefined
If I simply return the exception that is thrown within the getCompDoc method, I recieve the error as expected, however as soon as I use Promise.reject it resolves with undefined.
I was thinking of wrapping the getCompDoc in a return new Promise() however I wasn't sure if this would be an example of the Promise constructor anti-pattern. I would ideally like to reject this, instead of returning the error directly as then I can assert that the method was rejected and not fulfilled.
You 'swallow' the error in getCompDoc in your catch clause. Specifically, here's a simplified snippet representing your code:
let getYamlPromise = Promise.reject('REJECTED!');
let getCompDocPromise = getYamlPromise
.then(data => console.log('getYamlPromise', data))
.catch(error => console.error('getYamlPromise', error));
getCompDocPromise
.then(a => console.log('getCompDocPromise RESOLVED', a))
.catch(a => console.log('getCompDocPromise REJECTED', a));
As you can see, getCompDocPromise is resolved with undefined. If you would like to propagate the error, your catch clause will have to throw a new error or return a rejected promise:
let getYamlPromise = Promise.reject('REJECTED!');
let getCompDocPromise = getYamlPromise
.then(data => console.log('getYamlPromise', data))
.catch(error => {
console.error('getYamlPromise', error);
return Promise.reject(error);
});
getCompDocPromise
.then(a => console.log('getCompDocPromise RESOLVED', a))
.catch(a => console.log('getCompDocPromise REJECTED', a));

Javascript not catching error using fetch

I have an authService which has the following fetch
export const handleForm = () => {
return fetch('http://localhost:5000/login')
.then(response => {
if (!response.ok) {
throw new Error("Email or password incorrect");
} else {
return response.json();
}
})
.then(json => json)
.catch(error => error);
};
I'm calling the method from my controller:
form.onsubmit = function (e) {
handleForm(e)
.then(response => {
console.log(response);
//setAuthenticatedUser(response);
// setDomUser(response.user);
})
.catch(error => {
console.log(error);
document.querySelector('#error').innerHTML = error;
})
};
I've tried a few things to get the error in my catch, in the controller
But I keep getting the response callback fired.
The error from my authService isn't being caught
I've Tried
throw new Error();
Promise.reject();
See Promise.prototype.catch():
Syntax Section
p.catch(onRejected);
[...]
The Promise returned by catch() is rejected if onRejected throws an error or returns a Promise which is itself rejected; otherwise, it is resolved.
Using .catch(error => error) as a callback returns the exception. So it neither "throws an error" nor "returns a Promise which is itself rejected". The promise returned by it is thus resolved, not rejected.
Simply remove .catch(error => error) and it will work. And when you're at it, also remove .then(json => json) as it is useless.

Fetching multiple files using Promises and Fetch API javascript

I am updating my javascript skills with Promises, already have in place a library with XHR and callbacks to load and inject multiple files at once and only proceed if ALL of them succeeded.
I am trying to use Promise.all() and Fetch API to get a similar functionality but can't make it work: console.log('All the promises are resolved', values); always triggers no matter if some of the fetch promises failed.
I want to be able to execute the code below, and only proceed with nowInitialize function if all the files were able to be fetched, or throw error using catch() with the reason of the first file that failed
xRequire(['index.html', 'style.cds'])
.then(nowInitialize)
.catch(reason => 'One or more files failed to load' + reason)
style.cds will obviously fail
//TODO Handle file types appropriately
//TODO: Inject css, javascript files
function xRequire(files) {
let urls = [];
let promisesList = [];
let handleAllPromises;
//Populates urls with each file required
for(let i=0; i < files.length ; i++) {
urls.push(files[i]);
}
//Fetch each file in urls
urls.forEach( (url, i) => { // (1)
promisesList.push(
fetch(url)
.then(handleResponse)
.then(data => console.log(data))
.catch(error => console.log(error))
);
});
handleAllPromises = Promise.all(promisesList);
handleAllPromises.then(function(values) {
console.log('All the promises are resolved', values);
});
handleAllPromises.catch(function(reason) {
console.log('One of the promises failed with the following reason', reason);
});
}
function handleResponse(response) {
let contentType = response.headers.get('content-type');
console.log('Requested Info: ' + contentType);
if (contentType.includes('application/json')) {
return handleJSONResponse(response);
} else if (contentType.includes('text/html')) {
return handleTextResponse(response);
} else if (contentType.includes('text/css')) {
return handleTextResponse(response);
} else if (contentType.includes('application/javascript')) {
return handleTextResponse(response);
} else {
throw new Error(`Sorry, content-type ${contentType} not supported`);
}
}
function handleJSONResponse(response) {
return response.json()
.then(json => {
if (response.ok) {
return json;
} else {
return Promise.reject(Object.assign({}, json, {
status: response.status,
statusText: response.statusText
}));
}
});
}
function handleTextResponse(response) {
return response.text()
.then(text => {
if (response.ok) {
return text;
} else {
return Promise.reject({
status: response.status,
statusText: response.statusText,
err: text
});
}
});
}
Can you just rewrite it as async-await code? Here is a rough idea of the typical flow:
const [data1, data2, data3] = await Promise.all([
fetch(url1),
fetch(url2),
fetch(url3),
]);
In other words, Promise.all() returns the promise to all the data that is returned from your multiple fetch() functions.
Then, if you put this into a try-catch, you can handle the rejection as well:
try {
const [data1, data2, data3] = await Promise.all([
fetch(url1),
fetch(url2),
fetch(url3),
]);
// Now you can process the data:
[data1, data2, data3].map(handleResponse);
} catch (error) {
console.log('Error downloading one or more files:', error);
}
If you want to loop with async-await, you can do that:
const promises = [];
for (const url of [url1, url2, url3, url4]) {
promises.push(fetch(url));
}
const [data1, data2, data3, data4] = await Promise.all(promises);
There are two problems. First, you need to return the Promise.all call from xRequire in order to consume it in your xRequire(..).then:
return Promise.all(promisesList);
Also, when you use .catch, if a Promise is initially rejected, it will go into the catch block, do whatever code is there, and then the Promise chain will resolve (not reject) to whatever the catch block returns. If you want to percolate errors up the Promise chain, put your catch at the point in the chain at which you want to detect errors:
urls.forEach( (url, i) => { // (1)
promisesList.push(
fetch(url)
.then(handleResponse)
.then(data => console.log(data))
// no catch here
);
});
I would suggest putting your catch only in the caller of xRequire, that way it will see all errors. Your xRequire function can be reduced to:
xRequire(['index.html', 'style.cds'])
.then(nowInitialize)
.catch(reason => 'One or more files failed to load' + reason)
function xRequire(files) {
return Promise.all(
urls.map(handleResponse)
);
}
If you want the body of xRequire to be able to see errors, but you also want to percolate errors up the Promise chain, throw an error in a catch inside xRequire, so that the Promise it resolves to will reject, rather than resolve:
function xRequire(files) {
return Promise.all(
urls.map(handleResponse)
)
.catch((err) => {
console.log('There was an error: ' + err);
throw err;
})
}
I finally solved it in this way --with the only quirk i've found so far: files argument always needs to be an array, therefore always needs brackets when calling the function--
xRequire(['my-file'])
.then(handle success)
.catch(handle error);
async function xRequire(files) {
let promises = [];
let receivedData;
//Iterate over files array and push results of fetch to promises array
files.map(x => promises.push(fetch(x)));
//populate receivedData array from promises array
receivedData = await Promise.all(promises);
//iterate over receivedData to handle each response accordingly
return receivedData.map(x => handleResponse(x));
}

How do I fix a race condition in a nested Promise in Node.js? [duplicate]

I'm creating an API using Node.js/TypeScript running Express. Below is an excerpt from my get method. An error is being triggered in the format method, which throws an error that is caught by the promise, but not propagated to the parent promise after a throw:
this.getModel(objectName).findAll(queryParameters).then(function(databaseObjects) {
for (let databaseObject of databaseObjects) {
var jsonObject = {};
//console.log("Database object: ");
//console.log(databaseObject);
transform.baseFormat(databaseObject, jsonObject)
.then(() => transform.format(databaseObject, jsonObject))
.then(() => {
res.locals.retval.addData(jsonObject);
}).catch((e) => {
console.log("Caught error during format of existing object: ");
console.log(e);
throw e;
});
}
})
.then(() => {
if (metadata) {
this.metadata(objectName, false, transform, res.locals.retval);
delete queryParameters.limit;
delete queryParameters.offset;
console.log("RUNNING METADATA COUNT: ");
this.getModel(objectName).count(queryParameters).then(function(count) {
res.locals.retval.setMetadata("records", count);
return next();
}).catch(function(e) {
this.error(e, res);
return next();
});
} else {
console.log("NO METADATA");
return next();
}
})
.catch((e) => {
// TODO: Move status into error() function
console.log("500 Error on GET");
console.error(e);
res.locals.retval.addError(ErrorCode.InternalError, e);
res.status(ErrorCode.InternalError).send(res.locals.retval);
return next();
});
Here's the output:
(node:8277) Warning: a promise was created in a handler at /Library/WebServer/adstudio/dist/server.js:555:51 but was not returned from it, see
at Function.Promise.bind (/Library/WebServer/adstudio/node_modules/bluebird/js/release/bind.js:65:20)
Caught error during format of existing object:
Test Error
END FUNCTION HAS BEEN REACHED!
Then the request fails to finish.
I've read a lot on Promises and I haven't been able to find an issue/solution similar to mine.
http://bluebirdjs.com/docs/warning-explanations.html
http://taoofcode.net/promise-anti-patterns/
https://www.reddit.com/r/javascript/comments/4bj6sm/am_i_wrong_to_be_annoyed_with_promise_error/
https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html
Chained promises not passing on rejection
http://wiki.commonjs.org/wiki/Promises/A
https://promisesaplus.com/
Running inside that for-loop is not asynchronous, so your promise is resolving basically as soon as the loop finishes, yet before all your formatting finishes.
Use a promise control flow, like bluebird's Promise.each which is serial or just Promise.all. Then any exceptions will be caught.
this.getModel(objectName).findAll(queryParameters).then(function (databaseObjects) {
var promises = databaseObjects.map(databaseObject => {
var jsonObject = {}
// console.log("Database object: ");
// console.log(databaseObject);
return transform.baseFormat(databaseObject, jsonObject)
.then(() => transform.format(databaseObject, jsonObject))
.then(() => {
res.locals.retval.addData(jsonObject)
}).catch((e) => {
console.log('Caught error during format of existing object: ')
console.log(e)
throw e
})
})
return Promise.all(promises)
})
.catch((e) => {
// TODO: Move status into error() function
console.log('500 Error on GET')
console.error(e)
res.locals.retval.addError(ErrorCode.InternalError, e)
res.status(ErrorCode.InternalError).send(res.locals.retval)
return next()
})

Categories

Resources