This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 3 years ago.
I have the following code writed in nodejs express and firebase
route.js
try{
const test = await invoiceData.setAssignsInvoiced();
res.json({
status: true,
message: "Successful Invoice Generation"
});
}catch (e) {
res.status(500).json({
status: false,
message: "Internal Server Error",
data: e
});
}
InvoicesStorage.js
setAssignsInvoiced = async() => {
return new Promise(async (resolve,reject)=>{
try {
await _getAssignsForInvoiced(this);
this.assignsForInvoiced.forEach(async assing => {
let aux = assing._key.path.segments.length;
let ref = assing._key.path.segments[aux - 1];
await _updateAssignsToInvoiced(assing.data(),ref);
});
resolve(true)
} catch (error) {
console.error(error)
reject(error)
}
})
};
const _updateAssignsToInvoiced = async (assing, ref) => {
try {
const { invoiceNum } = assing.data(); //Here's an intentional error
await db
.collection("leadAsign")
.doc(ref)
.update({
invoiced: true,
updateDate: Date.now() - 240 * 60 * 1000,
invoiceNum
});
} catch (error) {
console.error(error);
throw new Error("Error at update to invoiced assigns");
}
};
How I hope it works:
According to me, I should throw out a synchronous error because my code has "await" and stop the system.
The answer I have:
the code runs asynchronously, that is, after calling the function the "await" has no effect and answers a "res.json" with status 200 and it is only after it throws the next error.
TypeError: assing.data is not a function
at _updateAssignsToInvoiced (D:\$Workzone\gd_fridays_h\src\controllers\invoices\InvoicesStorage.js:90:35)
at D:\$Workzone\gd_fridays_h\src\controllers\invoices\InvoicesStorage.js:55:23
at Array.forEach (<anonymous>)
at D:\$Workzone\gd_fridays_h\src\controllers\invoices\InvoicesStorage.js:51:37
true
POST /generateSingle 200 5182.650 ms - 57
(node:5600) UnhandledPromiseRejectionWarning: Error: Error at update to invoiced assigns
at _updateAssignsToInvoiced (D:\$Workzone\gd_fridays_h\src\controllers\invoices\InvoicesStorage.js:102:11)
at D:\$Workzone\gd_fridays_h\src\controllers\invoices\InvoicesStorage.js:55:23
at Array.forEach (<anonymous>)
at D:\$Workzone\gd_fridays_h\src\controllers\invoices\InvoicesStorage.js:51:37
(node:5600) 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: 4)
(node:5600) [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.
async/await doesn't work as you're expecting it to inside a forEach loop. An abundance of info on that specific issue here: https://stackoverflow.com/a/37576787/4043746
To fix your problem, you could use a for/of loop:
setAssignsInvoiced = async () => {
return new Promise(async (resolve, reject) => {
try {
await _getAssignsForInvoiced(this)
for (const assign of this.assignsForInvoiced) {
let aux = assign._key.path.segments.length
let ref = assign._key.path.segments[aux - 1]
await _updateAssignsToInvoiced(assign.data(), ref)
}
resolve(true)
} catch (error) {
console.error(error)
reject(error)
}
})
}
However, I'd also be tempted to suggest not returning a promise, as you're essentially doing that due to it being an async function. Something like this should work and is cleaner imo:
setAssignsInvoiced = async () => {
try {
await _getAssignsForInvoiced(this)
for (const assign of this.assignsForInvoiced) {
let aux = assign._key.path.segments.length
let ref = assign._key.path.segments[aux - 1]
await _updateAssignsToInvoiced(assign.data(), ref)
}
} catch (error) {
console.error(error)
// Re-throwing the error to pass the error down, just like you've
// done inside your _updateAssignsToInvoiced function's catch
throw new Error('Error setting assigns')
}
}
Async/await inside a forEach() loop will not wait until all the async operations inside the loop is completed.
One approach would be using Promise.all() like so:
const setAssignsInvoiced = async () => {
try {
await _getAssignsForInvoiced(this);
await _updateAssignsList(this.assignsForInvoiced);
return true;
} catch (error) {
console.error(error);
return new Error(error);
}
};
const _updateAssignsList = assignsList => {
return Promise.all(
assignsList.map(async assign => {
let aux = assign._key.path.segments.length;
let ref = assign._key.path.segments[aux - 1];
return await _updateAssignsToInvoiced(assign.data(), ref);
})
);
};
I've just extracted the async loop process to a separate function which return a Promise.
Related
If I have a promise rejection within a for that is not caught by a try/catch surrounding the for or the invoker method catch block
const asyncMethod = async (item) => {
return Promise.reject(item);
};
const sleep = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
const myMethod = async () => {
try {
const myArr = [1, 2,];
const promises = [];
for (const item of myArr) {
const myPromise = asyncMethod(item);
promises.push(myPromise);
await sleep(1);
}
await Promise.allSettled(promises);
} catch (e) {
console.log('handled by try catch');
}
};
myMethod().catch(() => {
console.log('handled by invoker');
});
If I run this i will get unhandled rejection, and I'm wondering why is it not hitting my catch block.
I'm not asking for a solution, I'm just trying to understand how V8 works.
This isn't just V8 - any spec-compliant implementation will produce an unhandled rejection with this code.
At the moment that a Promise rejects, there must be a .catch handler somewhere that has been attached to the Promise to catch the rejection. Otherwise, an unhandled rejection will occur - the engine can't look into the future to see if you attach a handler at a later time, it must be seen as attached right when the Promise rejects.
Because you do
const myPromise = asyncMethod(item);
promises.push(myPromise);
await sleep(1);
You're waiting for sleep before passing the promise that will reject to .allSettled (which will count as a rejection handler). If the Promise rejects before all the sleeps finish, there'll be an unhandled rejection.
(.allSettled does count as a reject handler, as you can see below:)
const asyncMethod = async (item) => {
return Promise.reject(item);
};
const sleep = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
const myMethod = async () => {
try {
const myArr = [1, 2,];
const promises = [];
for (const item of myArr) {
const myPromise = asyncMethod(item);
promises.push(myPromise);
// dummy rejection handler through .allSettled attached immediately, just to illustrate:
Promise.allSettled([myPromise]);
await sleep(1);
}
await Promise.allSettled(promises);
} catch (e) {
console.log('handled by try catch');
}
};
myMethod().catch(() => {
console.log('handled by invoker');
});
My unit test code is like this:
// function for my unit test to test exception
const mockServiceThrow = async () => { throw new Error('unit test error message'); };
const createContextAndDoc = () => new Promise((resolve, reject) => {
(async () => {
const res = await mockServiceThrow();
if (res === 1) resolve(1)
else reject(0);
})();
});
createContextAndDoc().catch((e) => {
console.log('--------');
console.log(e.message);
console.log('--------');
});
When i run this unit test:
./node_modules/.bin/jest local_modules/__test__/unhandledException.test.js
The complete output is like this:
RUNS local_modules/__test__/unhandledException.test.js
node:internal/process/promises:246
triggerUncaughtException(err, true /* fromPromise */);
^
[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: unit test error message".] {
code: 'ERR_UNHANDLED_REJECTION'
}
Don't see why it says unhandled, i do have .catch(). Any suggestions ?
Looks like you're invoking the async function right away, which causes the unit test error, and that is never cought since the promise is never returned - so the .catch is not catching anything. The error is not part of the promise chain.
If you want to invoke the function right away you need to catch the error and reject it so the promise finishes.
const mockServiceThrow = async () => { throw new Error('unit test error message'); };
const createContextAndDoc = () => new Promise((resolve, reject) => {
(async () => {
try {
const res = await mockServiceThrow();
if (res === 1) resolve(1)
else reject(0);
} catch (e) {
reject(e)
}
})();
});
createContextAndDoc().catch(e => {
console.log('------------------');
console.log(e.message);
console.log('--------')
})
You could also simplify your code a little:
const mockServiceThrow = async () => { throw new Error('unit test error message'); };
const createContextAndDoc = async () => {
const res = await mockServiceThrow();
if(res === 1) {
return Promise.resolve(1)
} else {
return Promise.reject(0);
}
};
createContextAndDoc().catch(e => {
console.log('------------------');
console.log(e.message);
console.log('--------')
})
Edit: Further explanation of promises.
createContextAndDoc is a function that returns a promise. That promise resolves if res === 1 but rejects if res is something else. For that to happen the mockServiceThrow promised must be resolved. Otherwise you won't get any value for res variable.If that happens then your promise function never fullfills (resolves or rejects).
In your case mockServiceThrow fails and throws an error, this error is not part of the promise you created with new Promis. To make sure your promise fullfills (resolves or rejects) you need the callbacks, otherwise the error is not part of the promise.
The simplified code has one async function so that when mockServiceThrow fails its part of the async function that you're trying to catch.
I have the following code, it works but it still throws the warning.
I'm running in Node v12.
(node:15985) 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: 3)
Here is the source code for the loop, thanks:
const links = await knex('links').where('active', true);
const links = await knex('links').where('active', true).catch((e) => console.log(e)) // does not work neither
for (let index = 0; index < links.length; index++) {
const element = links[index];
console.log('Running with', index, element.uri);
(async () => {
try {
const { statusCode } = await got({
url: `${element.protocol}://${element.uri}:${element.port}`,
method: 'GET',
timeout: 5000
})
const check = {
statusCode: statusCode,
}
await knex('checks').insert(check);
} catch (error) {
const check = {
status: 'error',
}
await knex('checks').insert(check);
}
})().catch(() => {});
}
You have 2 promises without a catch block:
As pointed by #Phix in the comment, one on the first line:
// On the first line
await knex('links').where('active', true)
In the catch block at the end of the code:
await knex('checks').insert(check);
I made some change to the original code according to my understanding. I also suspect await knex('checks').insert(check); is the issue for the unhandled promise. So I added a try & catch to handle it. The finally is not required. I just want to make the flow a bit clear. I hope it solve the issue, at least provide some ideas.
// assign the async salad to a const to make the flow a bit more clear
const fetchData = async () => {
try {
let check;
const { statusCode } = await got({
url: `${element.protocol}://${element.uri}:${element.port}`,
method: 'GET',
timeout: 5000
})
check = {
statusCode: statusCode,
};
} catch (error) {
check = {
status: 'error',
}
} finally {
// use a try catch to handle the potential async call
try {
await knex('checks').insert(check);
} catch {
throw new Error('Error')
}
}
};
// if await knex('checks').insert(check) fails, we can catch the error it throws
try {
fetchData()
} catch (erroe) {
console.log(error);
}
This question already has answers here:
Waiting for more than one concurrent await operation
(4 answers)
Closed 2 years ago.
By using node js 12.16.1 LTS
I don't understand why this piece of code leads to a double rejection (one unhandled and one catched).
When I remove the p promise and await p in create_bug(), it works well (Only one rejection catched in a try catch block). I cannot figure out why.
Nodejs experts, could you please help ?
'use strict';
process.on('uncaughtException', (err) => {
console.error(`uncaughtException: ${JSON.stringify({name: err.name, msg: err.message})}`);
});
process.on('unhandledRejection', (err) => {
console.error(`unhandledRejection: ${JSON.stringify({name: err.name, msg: err.message})}`);
});
async function create_bug() {
console.log('In create');
let res = superCreate();
console.log(`In create, res = ${res}`);
let p = new Promise((a, r) => setTimeout(() => a(), 0));
await p;
return res;
}
async function superCreate() {
console.log('superCreate : now throwing');
throw new Error("Something wrong");
}
async function create_OK() {
console.log('In create');
let res = await superCreate();
console.log(`In create, res = ${res}`);
let p = new Promise((a, r) => setTimeout(() => a(), 0));
await p;
return res;
}
async function main() {
try {
let res = await create_bug();
console.log(`create result : ${res}`);
} catch (err) {
console.error(`ERROR caught in main : ${JSON.stringify({name: err.name, msg: err.message})}`);
}
}
main().then(() => {
setTimeout(() => console.log(`Finished`), 2000);
});
The promise contained in the variable res from your superCreate is not awaited and there is not attached a catch handler to it before it gets rejected. Therefore the unhandled promise rejection is triggered. A handler is attached after the rejection when the await is triggered in main.
Note that a rejection handler is invoked even though it is attached on a promise after it is rejected. Try e.g.:
async function main() {
let res = create_bug();
try {
await res;
console.log(`create result : ${res}`);
} catch (err) {
console.error(`ERROR caught in main : ${JSON.stringify({name: err.name, msg: err.message})}`);
}
res.catch(err => console.error(`main1: ${JSON.stringify({name: err.name, msg: err.message})}`));
res.catch(err => console.error(`main2: ${JSON.stringify({name: err.name, msg: err.message})}`));
}
Notice that you will now also get the "main1" and "main2" errors.
Alternatively, try removing the async from the superCreate function, now you should see that the In create, res = ${res} is not printed, but instead the exception is handled synchronously.
Yet another alternative is to simply return res directly from create_bug without any await and instead await the res in main. Then you will see similar behavior to your original: both an unhandled rejection and the "normal" catch-handling block.
async function create_bug() {
console.log('In create');
let res = superCreate();
console.log(`In create, res = ${res}`);
return res;
}
async function superCreate() {
console.log('superCreate : now throwing');
throw new Error("Something wrong");
}
async function main() {
try {
let res = create_bug();
let p = new Promise((a, r) => setTimeout(() => a(), 0));
await p;
await res;
console.log(`create result : ${res}`);
} catch (err) {
console.error(`ERROR caught in main : ${JSON.stringify({name: err.name, msg: err.message})}`);
}
}
im trying to get the promise error, and throw to the "try catch" to concentrate the return of the error in one place.
like this:
async schedule(req, res) {
try {
//here is the function that returns a promise
service.search()
.then(async data => {
if (data.length > 0) {
res.status(200).json("OK!");
}
})
.catch(async error => {
//here i want to throw this error to the "try catch" to return the error message
throw new Error(error);
})
}
catch (error) {
res.status(400).json(error);
};
}
but when goes to "throw new Error(error);" gives me the message:
(node:15720) 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)
warning.js:27
someone could help me to understand what im doing wrong?
thank so much!
Rafael
UPDATE
based on the Marcos answer, i did:
async schedule(req, res) {
try {
const data = await service.search();
if (data.length > 0) {
res.status(200).json("OK!");
}
}
catch (error) {
res.status(400).json(error);
};
}
and worked... Now i understand how to handle this errors... thanks!
You either use async/await with a try/catch or .then/.catch, you don't mix both ways.
async schedule(req, res) {
try {
//here is the function that returns a promise
// If service.search rejects, it will go to the `catch`
const data = await service.search()
if (data.length > 0) {
return res.status(200).json("OK!");
}
// do something here
// res.status(400).send('Invalid data')
// throw new Error('Invalid data')
} catch (error) {
res.status(400).json(error);
}
}
or
schedule(req, res) {
service.search()
.then(data => {
if (data.length > 0) {
res.status(200).json("OK!");
}
})
.catch(error => {
res.status(400).json(error);
})
}