I am working on a project that involves updating the entire mongoDB collection everyday.
I am making multiple asynchronous calls so I decide to use async/await and try/catch. My code looks like this:
const updateMongoData = async () => {
try {
const data = await getData(); //This is the new data that I am using to update Mongo docs
const countries = await GlobalData.find();
countries.forEach(async (row) => {
const res = await Global.updateOne(
{ country: row.country },
{
//Use data
lastUpdated: Date.now(),
}
);
});
} catch (err) {
console.log(err);
}
};
Everything works fine except if I make a syntax error e.g Dated.now() instead of Date.now(). This is will give my an error saying
(node:8248) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 368)
I tried inserting another try catch instead my forEach and moved the forEach inside and this seems to catch the syntax error. Why does this work? And what is the cleanest way to do this?
GlobalData.find()
.then(countries => {
countries.forEach(async (row) => {
try{
const res = await Global.updateOne(
{ country: row.country },
{
//Use data
lastUpdated: Date.now(),
}
);
}
catch(err){
console.log(err);
}
})
}).catch(err => console.log(err));
Your try catch is unable to catch the ReferenceError thrown by changing Date to Dated because catch requires a call to the implicit async/await Promise chain's reject handler. Errors thrown inside Array.prototype.forEach will not see that handler.
You can think about Array.prototype.forEach as a method that starts off a whole slew of runaway executions; none of which are meant to be awaited from the parent scope
You can use Promise.all and Array.prototype.map to catch the error instead.
const updateMongoData = async () => {
try {
const data = await getData(); //This is the new data that I am using to update Mongo docs
const countries = await GlobalData.find();
await Promise.all(countries.map(async (row) => {
const res = await Global.updateOne(
{ country: row.country },
{
//Use data
lastUpdated: Dated.now(),
}
);
}))
} catch (err) {
console.log('yo', err);
}
};
updateMongoData() // > yo ReferenceError: Dated is not defined
Your second example works because the try catch block is sharing the same scope as the Array.prototype.forEach block. If you moved that try catch statement outside, however, it will not see the error.
There is another way using a project I started. Contributions are welcome.
const { pipe, fork, map, tryCatch } = require('rubico')
const updateGlobalByCountry = (country, data) => Global.updateOne(
{ country },
{ lastUpdated: Date.now() }, // <- use data
)
const updateMongoData = tryCatch(
pipe([
fork({
data: getData, // () => data
countries: GlobalData.find, // () => countries
}), // () => ({ data, countries })
({ data, countries }) => map(
country => updateGlobalByCountry(country, data)
)(countries), // ({ data, countries }) => updatedCountries
]),
err => console.log(err),
)
Related
We are currently upgrading from Node v12 to Node v16.
After the node version update, the following test is failing with this error.
[UnhandledPromiseRejection: This error originated either by throwing
inside of an async function without a catch block, or by rejecting a
promise which was not handled with .catch(). The promise rejected with
the reason "Error".] { code: 'ERR_UNHANDLED_REJECTION'
But why does a node version cause this.
This error stops if I remove the .catch() from the test case.
But it is still incorrect outcome cos the snapshot no longer matches if I do that.
This is the test.
it('should test', async () => {
const thePromise = Promise.resolve({
json: () => {
Promise.reject(new Error());
},
});
// validateData is fetch call.
validateData.mockReturnValueOnce(thePromise);
const rendered = renderModule({ state: { code: 'mockRefCode' } });
rendered.find('#btn').simulate('click');
await ((thePromise)).then().catch(); // If i remove this catch, above error gone but wrong snapshot (null snapshot).
expect(rendered.find('#error')).toMatchSnapshot();
});
This is the code being tested.
export const validateCode = async (env, code) => fetch(`www.getapiresponse.example`);
export const SomeReactComponent(props) {
…
const [validatingCode, setValidatingCode] = useState(false);
const [validationError, setValidationError] = useState('');
const validateData = () => {
if (!code) {
setValidationError(langPack.emptyField);
return;
}
setValidatingCode(true);
validateCode(environment, code.trim())
.then((response) => response.json())
.then((body) => {
if (body.response_status_code !== '200') {
// some logging
} else {
setValidationError('');
}
})
.catch(() => setValidationError(langPack.serviceError))
.finally(() => setValidatingCode(false));
};
…
}
If I remove the .catch(), the snapshot ends up as null.
The correct snapshot should be something like following:
- <div
- className="error"
- id="error"
- >
- service
- </div>
The problem is that i am getting UNhandledPromiseRejection error eveen though i think i have handled all the cases. The code flows from profileRoutes to Controller to Utils where the error comes first.
Inside the profileRoutes.js
router.get('/:username', async (r, s) => {
try{
let profileData = await getProfileData(r.params.username);
s.json({ success: true, payload: profileData });
}catch(err){
console.log('ending request processing by responding a error');
s.status(500).json({ success: false, message: 'err[0].message' });
}
});
Inside the controllers/index.js
const fetchQueue = [getUserRepos];
async function getProfileData(username) {
let profileData = {};
try{
let results = await Promise.all(fetchQueue.map(item => item(username)));
for (let i = 0; i < results.length; i++) {
profileData[getKeys[i]] = results[i];
}
return profileData;
}catch(err){
console.log('error log in controller/index getProfileData function');
throw err;
}
}
const getUserRepos = async (username) => {
try {
// const res = await utils.gqlSender(username, 'userRepos', { createdAt });
const res = await utils.gqlSender(username, 'userReposData');
return res.user.repositories;
} catch (err) {
console.log('error log in controller/index getUserRepos function');
throw err;
}
};
Inside the utils/index.js
const gqlSender = async (username, type, opt = {}) => {
axios.post('', {
query: gqlQuery(username, type, opt) // generates a needed graphQL query
}).then(res => {
if(res.data.errors) { // this is where the error is recieved and so i reject promise.
console.log('bef###re');
return Promise.reject (res.data.errors);
}
console.log('###',res.data);
return res.data;
}).catch(err => {
console.log('error in making axios request inside utils/index gqlSender function');
throw err;
// return Promise.reject(err);
});
The stack trace on making get request to /:username is-
error log in controller/index getUserRepos function
error log in controller/index getProfileData function
ending request processing by responding a error
bef###re
error in making axios request inside utils/index gqlSender function
(node:11260) UnhandledPromiseRejectionWarning: [object Array]
(node:11260) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
(node:11260) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
I dont think i am missing any Promise Rejection.
Any help is appreciated. Thanks.
i have referred these answers previously -
What's the difference between returning value or Promise.resolve from then()
Do I need to return after early resolve/reject?
Your gqlSender function is not returning the promise that will get rejected, so it is not handled anywhere. You should write either
const gqlSender = (username, type, opt = {}) => {
return axios.post('', {
// ^^^^^^
query: gqlQuery(username, type, opt) // generates a needed graphQL query
}).then(res => {
if (res.data.errors) {
console.log('error in making axios request inside utils/index gqlSender function');
throw res.data.errors;
} else {
console.log('###',res.data);
return res.data;
}
});
};
or
const gqlSender = async (username, type, opt = {}) => {
// ^^^^^
const res = await axios.post('', {
query: gqlQuery(username, type, opt) // generates a needed graphQL query
});
if (res.data.errors) {
console.log('error in making axios request inside utils/index gqlSender function');
throw res.data.errors;
} else {
console.log('###',res.data);
return res.data;
}
}
I am using Promise.prototype.finally() (or try-catch-finally in an async function) in my production code to execute some follow-up code without changing resolution/rejection status of the current promise.
However, in my Jest tests, I would like to detect that the Promise inside finally block wasn't rejected.
edit: But I don't want to actually await the Promise in my "production" code (there I care only about errors re-thrown from catch, but not about errors from finally).
How can I test for that? Or at least how to mock the Promise.prototype to reject the current promise on exceptions from finally?
E.g. if I would be testing redux action creators, the tests pass even though there is a message about an unhandled Promise rejection:
https://codesandbox.io/s/reverent-dijkstra-nbcno?file=/src/index.test.js
test("finally", async () => {
const actions = await dispatchMock(add("forgottenParent", { a: 1 }));
const newState = actions.reduce(reducer, undefined);
expect(newState).toEqual({});
});
const dispatchMock = async thunk => {...};
// ----- simplified "production" code -----
const reducer = (state = {}, action) => state;
const add = parentId => async dispatch => {
dispatch("add start");
try {
await someFetch("someData");
dispatch("add success");
} catch (e) {
dispatch("add failed");
throw e;
} finally {
dispatch(get(parentId)); // tests pass if the promise here is rejected
}
};
const get = id => async dispatch => {
dispatch("get start");
try {
await someFetch(id);
dispatch("get success");
} catch (e) {
dispatch("get failed");
throw e;
}
};
const someFetch = async id => {
if (id === "forgottenParent") {
throw new Error("imagine I forgot to mock this request");
}
Promise.resolve(id);
};
dispatch(get(parentId)); // tests pass if an exception is thrown here
There is no exception throw in that line. get(parentId) might return a rejected promise (or a pending promise that will get rejected later), but that's not an exception and won't affect control flow.
You might be looking for
const add = parentId => async dispatch => {
dispatch("add start");
try {
await someFetch("someData");
dispatch("add success");
} catch (e) {
dispatch("add failed");
throw e;
} finally {
await dispatch(get(parentId));
// ^^^^^
}
};
Notice that throwing exceptions from a finally block is not exactly a best practice though.
edit: more general solutions available on https://stackoverflow.com/a/58634792/1176601
It is possible to store the Promise in a variable accessible in some helper function that is used only for the tests, e.g.:
export const _getPromiseFromFinallyInTests = () => _promiseFromFinally
let _promiseFromFinally
const add = parentId => async dispatch => {
...
} finally {
// not awaited here because I don't want to change the current Promise
_promiseFromFinally = dispatch(get(parentId));
}
};
and update the test to await the test-only Promise:
test("finally", async () => {
...
// but I want to fail the test if the Promise from finally is rejected
await _getPromiseFromFinallyInTests()
});
I have an array of data objects about people. Each person object includes 0-n URLs for additional info (guests of the person).
I want to process this list, calling each of the 'guest' URLs and including the guest's names in the original data set.
Context: this is an AWS lambda function. I'm using lambda-local to run this locally. (lambda-local -l index.js -e fixtures/test_event1.json).
I'm successfully using await/async to retrieve the initial set of data.
But I'm unable to get if working for these further calls about guest info. It always shows a pending Promise, even though the result is awaited.
// index.js
const fetch = require('node-fetch');
exports.handler = async function(event){
try {
let registrations = await getEventRegistrations();
console.log(registrations);
/* All good here - sample console output
[ { contactId: 43452967,
displayName: 'aaa, bbb',
numGuests: 0,
guestUrls: [] },
{ contactId: 43766365,
displayName: 'bbb, ccc',
numGuests: 1,
guestUrls:
[ 'https://<URL>' ] },
{ contactId: 43766359,
displayName: 'ccc, ddd',
numGuests: 2,
guestUrls:
[ 'https://<URL>',
'https://<URL> ] } ]
*/
// Expanding the guest URLs is proving problematic - see expandGuests function below
registrations = registrations.map(expandGuests);
console.log(registrations);
/* Registrations are just pending Promises here, not the real data
[ Promise { <pending> },
Promise { <pending> },
Promise { <pending> } ]
*/
return {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify(registrations),
};
}
catch (exception) {
console.log(exception);
return {
statusCode: 500,
body: 'Unable to retrieve data.'
};
}
};
function getEventRegistrations() {
return fetch('<URL>')
.then(res => res.json())
.catch(function (error) {
console.log('Event registrants request failed', error);
return null;
});
}
function getGuestName(url) {
return fetch(url)
.then(res => res.json())
.then(guest => guest.DisplayName)
.catch(function (error) {
console.log('Guest name request failed', error);
return null;
});
}
async function expandGuests(data) {
const promises = data.guestUrls.map(url => getGuestName(url));
data.guestNames = await Promise.all(promises);
return data;
}
How can I resolve these pending Promises and thus return useful data?
Thank you.
array.map doesn't contain any logic to wait for promises. It just calls the function for each element of the array, and then synchronously produces an array of the results. If you pass in async functions, then the result be an array of promises (since all async functions return promises).
You'll need to use Promise.all to create a promise which will resolve once all the individual promises have resolved, and then await that.
const promises = data.guestUrls.map(url => getGuestName(url));
data.guestNames = await Promise.all(promises);
P.S, i strongly recommend against mixing async/await with .then. The cases where you actually need to do so are very rare, so most of the time you just make the code harder to understand. For example, this function:
async function getGuestName(url) {
return await fetch(url)
.then(res => res.json())
.then(guest => guest.DisplayName)
.catch(function (error) {
console.log('Guest name request failed', error);
return null;
});
}
Should either be done like this:
async function getGuestName(url) {
try {
const res = await fetch(url);
const guest = await res.json();
return guest.DisplayName;
} catch (error) {
console.log('Guest name request failed', error);
return null;
}
}
Or like this
function getGuestName(url) {
return fetch(url)
.then(res => res.json())
.then(guest => guest.DisplayName)
.catch(function (error) {
console.log('Guest name request failed', error);
return null;
});
}
The comments are correct in pointing out that mapping an async function will return an array of Promises. What they don't explicitly mention is that you have two maps, and which one is problematic.
The issue is in the line:
reservations = reservations.map(expandGuests);
Any time you use map, you'll need to actually resolve the promises that return.
So:
const mappedPromises = reservations.map(expandGuests); //returns an Array of Pending promises
const mappedReservations = await Promise.all(mappedPromises); //resolves the promises
I have a simple async function. It just sends a request and returns the data:
export const updatePanorama = async ({ commit }, payload) => {
const urlEnd = '/v1/pano/update'
const type = 'post'
const resp = await api.asyncRequest(urlEnd, type, payload)
commit('SET_PANORAMA', resp.data)
return resp
}
And this is how I'm using the function:
handleUpdatePanorama (panorama) {
const payload = {}
this.updatePanorama(payload).then(resp => {
this.setIsLoading(false)
this.handleAlert('updateSuccess', 'success')
}).catch(() => {
this.setIsLoading(false)
this.handleAlert('updateError', 'danger')
})
},
The problem is, the code after catch runs if there's an error inside then. But this way I don't know whether the catch error is an request error or and error triggered by the code inside then.
I'm trying try and catch to solve that problem:
handleUpdatePanorama (panorama) {
try {
const payload = {}
const resp = await this.updatePanorama(payload)
console.log('resp:', resp)
this.setIsLoading(false)
this.handleAlert('updateSuccess', 'success')
} catch (err) {
this.setIsLoading(false)
this.handleAlert('updateError', 'danger')
})
},
However, I get an unexpected token error in this line: await this.updatePanorama(payload)
What am I doing wrong?
The problem is, the code after catch runs if there's an error inside then
The solution for that is to not use catch, but the second then parameter. Have a look at the difference between .then(…).catch(…) and .then(…, …) for details.
I'm trying try and catch to solve that problem
That won't work, the catch clause will still be called if there's an exception thrown by setIsLoading or handleAlert.
I get an unexpected token error. What am I doing wrong?
You have not declared the handleUpdatePanorama method as async.
To mitigate the issues and fix the syntax, you could write
async handleUpdatePanorama (panorama) {
var result
try {
const payload = {}
const resp = await this.updatePanorama(payload)
console.log('resp:', resp)
result = ['updateSuccess', 'success']
} catch (err) {
result = ['updateError', 'danger']
} finally {
this.setIsLoading(false)
}
this.handleAlert(...result)
},
If you need to handle errors specifically from updatePanorama, use the second argument to .then(onSuccess, onError)
handleUpdatePanorama(panorama) {
const payload = {}
this.updatePanorama(payload).then(resp => {
this.setIsLoading(false)
this.handleAlert('updateSuccess', 'success')
}, err => {
// handle error from updatePanorama
// you can throw err if you also want to handle the error in .catch()
}).catch(() => {
this.setIsLoading(false)
this.handleAlert('updateError', 'danger')
})
}
note: if you return (or have no return statement) from the error handler, any subsequent .then(onSuccess will execute, if you throw an error (or return Promise.reject() for example, then the .catch() code will also run