I'm having an issue with a function not awaiting for a promise.
I have a route with a post method, that pushes the body to the controller.
The controller, will then send it to the middleware to actually process the save to a MongoDB.
The middleware will return the the promised object back to the controller.
The controller is finishing with out awaiting the completion of the middleware.
The middleware is async, and the controller has an await and then a .then even following further.
Thoughts?
Controller
export async function createPlayer(req,res) {
//creates a Player in the database
try {
let results = '';
results = await PlayerFunction.createPlayer(req.body)
.then(console.log(results))
.then(res.status(200).send(results))
} catch (error) {
log_error('Errored trying to create Player in controller.', error);
res.status(404).send('Error Has Occured. An Admin has been contacted.');
}
};
Middleware
export async function createPlayer(req_body) {
//creates a Player in the database
try {
//set new player object
let new_Player = new Player(req_body);
new_Player.save((error) => {
if (error) {
log_error('Trying to create a Player.', error)
return { success: 'error', body: 'Error Has Occured. An Admin has been contacted.' };
} else {
console.log(new_Player);
return { success: true, body: new_Player };
};
});
} catch (error) {
log_error('Errored trying to create a player.', error);
return { success: 'error', body: 'Error Has Occured. An Admin has been contacted.' };
}
};
I tried to use .then promises to catch the returned results from the middleware and then return the results to the user.
What I have found is that the .save((error) => function doesn't follow the await made by the call it self. If you remove the error handling on the Save and rely on the catch in the overall try catch it will follow the await.
Controller:
export async function createPlayer(req,res) {
//creates a Player in the database
try {
res.status(200).send(await PlayerFunction.createPlayer(req.body));
} catch (error) {
log_error('Errored trying to create Player in controller.', error);
res.status(404).send('Error Has Occured. An Admin has been contacted.');
}
};
Middlewear
export async function createPlayer(req_body) {
//creates a Player in the database
try {
let new_Player = new Player(req_body);
await new_Player.save();
return { success: true, body: new_Player };
} catch (error) {
log_error('Errored trying to create a player.', error);
return { success: 'error', body: 'Error Has Occured. An Admin has been contacted.' };
}
};
In the async function needs to return promise-based value.
A Promise which will be resolved with the value returned by the async function, or rejected with an exception thrown from, or uncaught within, the async function.
So in your middleware createPlayer() needs to return Promise-based value.
export async function createPlayer(req_body) {
//creates a Player in the database
try {
//set new player object
let new_Player = new Player(req_body);
new_Player.save((error) => {
if (error) {
log_error('Trying to create a Player.', error)
return Promise.reject(JSON.stringify({ success: 'error', body: 'Error Has Occured. An Admin has been contacted.' }));
} else {
console.log(new_Player);
return Promise.resolve(JSON.stringify({ success: true, body: new_Player }));
};
});
} catch (error) {
log_error('Errored trying to create a player.', error);
return Promise.reject(JSON.stringify({ success: 'error', body: 'Error Has Occured. An Admin has been contacted.' }));
}
};
Related
I am creating a loop to create/update users using functions with built-in promises:
for (const user of usersjson.users) {
let getuser = getUser(url, okapikey, user[fieldMap.externalSystemId],
'externalSystemId'); //Check if user exists on the server
await getuser
.then(async (data) => {
if (data.users.length != 0) { //If user exists in the array
update = updateUser(url, okapikey, createduser, data.users[0].id);//Create update function
promises.push(update); //Store function in array
i++;
} else {
create = createNewUser(url, okapikey, createduser);//Create create function
promises.push(create); //Store function in array
i++;
}
}).catch((err) => {
console.error(err);
});
if (promises.length == 50 || i == usersjson.users.length) {//Run functions in batches of 50
await Promise.allSettled(promises)
.then((responses)=> {
for (const response of responses) { //For each promise response
if (response.status == 'fulfilled') { //If fulfilled
if (response.value.status == 204) {
console.log(`${response.value.status}: User ${response.value.request.path.substring(7)} was updated.`);
} else {
if (response.value.status == 201 && response.value.headers.location) {
console.log(`${response.value.status}: User ${response.value.headers['location']} was created.`);
} else {
console.log(response.value.headers.location);
}
}
} else { //Handle rejections
console.log(`There was an error with the user:${response.value}`);
}
}
}).catch((err)=> {
console.log(err);
});
promises=[]; //Empty Promise array
}
}
async function updateUser(url, token, user, userid)
{
return new Promise((resolve, reject) => {
//Create headers for put request
const options = {
method: "put",
headers: {
'x-okapi-token': token,
'x-okapi-tenant':'tenant',
'Content-type':"application/json"
}
};
//Make API get call
user.id=userid; //Adding the required field ID to the JSON
axios.put(`${url}/users/${userid}`, JSON.stringify(user), options)
.then(response => {
if (response.status == 204) {
resolve(response);
} else {
reject(`Error Code: ${err.response.status}\nError Text: ${err.response.data.errors[0].message}\nError Status: ${err}`);
}
}).catch((err) => {
console.error(`Error Code: ${err.response.status}`);
if (typeof err.response.data == 'string') {
console.error(err.response.data);
reject(`Error Code: ${err.response.status}\nError Text: ${err.response.data.errors[0].message}\nError Status: ${err}`);
} else if (err.response.data.errors[0].message) {
console.error(`Error Text: ${err.response.data.errors[0].message}`);
reject(`Error Code: ${err.response.status}\nError Text: ${err.response.data.errors[0].message}\nError Status: ${err}`);
} else {
reject(`Error Code: ${err.response.status}\nError Text: ${err.response.data.errors[0].message}\nError Status: ${err}`);
}
console.log(err.response);
});
});
};
async function createNewUser (url, token, user) {
return new Promise((resolve, reject) => {
//Create headers for put request
const options = {
headers: {
'X-Okapi-token': token,
'Content-type':"application/json"
}
};
//Make API get call
axios.post(`${url}/users`, JSON.stringify(user), options)
.then(response => {
if (response.status == 201) {
resolve(response);
} else {
reject(`Error Code: ${err.response.status}: ${user.externalSystemId},\nError Text: ${err.response.data.errors[0].message},\nError Status: ${err}`)
}
}).catch((err) => {
console.error(`Error on ${user.externalSystemId}: ${err}`);
if (err.response.data && typeof err.response.data == 'string') {
console.error(err.response.data);
reject(`Error Code: ${err.response.status}: ${user.externalSystemId},\nError Text: ${err.response.data.errors[0].message},\nError Status: ${err}`)
} else if (err.response.data.errors[0].message) {
console.error(`Error Text: ${err.response.data.errors[0].message}`);
reject(`Error Code: ${err.response.status}: ${user.externalSystemId},\nError Text: ${err.response.data.errors[0].message},\nError Status: ${err}`)
} else {
reject(`Error Code: ${err.response.status}: ${user.externalSystemId},\nError Text: ${err.response.data.errors[0].message},\nError Status: ${err}`)
}
});
});
};
const getUsers = (url,user,password) =>
{
return new Promise((resolve, reject) => {
//Create headers for POST request
const options = {
method: 'post',
headers: {
'Authorization': 'Basic '+Buffer.from(`${user}:${password}`).toString('base64')
}
}
//Make API get call
axios.get(url, options)
.then(response => {
resolve(response.data);
}).catch((err) => {
console.error(err);
reject(err);
});
});
};
The code and loop works fine when every promise is fulfilled, but once a promise is rejected, the loop breaks. I get the error message, for example:
Error on XXX: Error: Request failed with status code 422 Error Text:
User with this username already exists
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 Code: 422: XXX, Error Text: User with this username
already exists, Error Status: Error: Request failed with status code
422".] { }
Looking at the code and error, I believe this comes from the "createNewUser" function.
I'm not sure why the code breaks - I added catches to all the functions, handled rejections, and added catch statements in the main body of the code, but the loop still breaks.
What I need is for the loop to continue as usual even if one function fails (I will later change the log from console.log to an actual log file).
update = updateUser(url, okapikey, createduser, data.users[0].id);//Create update function
promises.push(update); //Store function in array
create = createNewUser(url, okapikey, createduser);//Create create function
promises.push(create); //Store function in array
This is inaccurate. You are not storing functions in that array, you actually call the updateUser/createNewUser functions here and store the resulting promises in the array. Then, your loop goes on to sequentially (because of the await) do more getUser operations before actually calling Promise.allSettled on the promises array. In the meantime, some of the promises might already have been rejected without having any handlers attached to them.
This is basically the same problem as discussed in Waiting for more than one concurrent await operation and Any difference between await Promise.all() and multiple await?.
To fix it, collect actual functions that you can execute later in your array:
let functions = [];
for (const user of usersjson.users) {
i++;
try {
const data = await getUser(url, okapikey, user[fieldMap.externalSystemId], 'externalSystemId');
if (data.users.length != 0) {
functions.push(() =>
// ^^^^^
updateUser(url, okapikey, createduser, data.users[0].id)
); // Create update function and store it in array
} else {
functions.push(() =>
// ^^^^^
createNewUser(url, okapikey, createduser)
); // Create create function and store it in array
}
} catch(err) {
console.error(err);
}
if (functions.length == 50 || i == usersjson.users.length) { // in batches of 50
const promises = functions.map(fn => fn()); // Run functions
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
const responses = await Promise.allSettled(promises);
for (const response of responses) {
if (response.status == 'fulfilled') {
if (response.value.status == 204) {
console.log(`${response.value.status}: User ${response.value.request.path.substring(7)} was updated.`);
} else {
if (response.value.status == 201 && response.value.headers.location) {
console.log(`${response.value.status}: User ${response.value.headers['location']} was created.`);
} else {
console.log(response.value.headers.location);
}
}
} else {
console.log(`There was an error with the user:${response.value}`);
}
}
functions = []; // empty functions array
}
}
(I've tried to avoid awaiting any .then(…) chains)
At the moment, for example, when deleting a record using Postman, a hang occurs and no error is displayed there. How can immediately display this error in Postman if it appears?
module.exports.remove = async function (req, res) {
try {
let category = await SprCategories.findOne({ CATEGORY_ID: req.params.CATEGORY_ID })
category.destroy().then(() => {
res.status(200).json(category)
})
} catch (e) {
errorHandler(res, e)
}
}
errorHandler:
module.exports = (res, error) => {
res.status(500).json({
success: false,
message: error.message ? error.message : error
})
}
It looks like your Unhandled rejection Error is coming from category.destroy().
To solve this since you are already using an async funcion, await category.destroy() if it fails your catch block will take over
module.exports.remove = async function (req, res) {
try {
const category = await SprCategories.findOne({ CATEGORY_ID: req.params.CATEGORY_ID })
await category.destroy()
res.status(200).json(category)
} catch (e) {
errorHandler(res, e)
}
}
I have created a nodejs/express api, which is a wrapper around another api.
I am making async calls with try and catch. I am wondering in the inner async call if it catches an error will this error be caught by the outer async call.
The transaction.controller is my api which calls the transaction.repository to an external api. The function getTransactionResult is throwing an error in the catch part. However, this does NOT loop back to the catch part of my show function.
router.ts
this.router.get('/:id', this.controller.show.bind(this.controller));
transaction.controller.ts
public async show(req: Request, res: Response): Promise<any> {
const params = req.params;
const token = req.headers.token;
let transaction;
try {
transaction = await this.transactionRepo.getTransactionResult(params.id, token);
console.log("instead carries on here"); // 2
if (transaction.success === false) {
return res.status(404).json({
success: false,
status: 404,
data: transaction,
message: "Failed to retrieve transaction result"
});
}
return res.status(200).json({
success: true,
status: 200,
data: transaction,
message: "Successfully retrieved transaction result"
});
} catch (err) {
//console.log("Does not call this");
return res.status(500).json({
success: false,
status: 400,
data: err,
message: "The server threw an unexpected error",
});
}
transaction.repository.ts
public async getTransactionResult(transactionId: string, token: string) {
const url = this.config.api_url + "/api/transactions/" + transactionId + "/summary";
const options = {
uri: url,
headers: {
'token': token
},
body: {
},
json: true,
resolveWithFullResponse: true
}
let result;
try {
result = await request.get(options);
if (result.statusCode !== 200) {
return { "success": false, data: result }
}
return { "success": true, data: result }
} catch (err) {
console.log("CAUGHT ERROR"); // 1
return err;
}
}
You need to rethrow the error not return it.
By doing return err you are resolving the Promise which mean the async operation succeeded. Hence why your out try-catch in transaction.controller.ts does not catch anything.
Either:
Don't catch the error and let it bubble up
Rethrow the error
I have the following code:
"use strict";
const Raven = require("raven");
Raven.config(
"test"
).install();
module.exports = function(Reservation) {
function dateValidator(err) {
if (this.startDate >= this.endDate) {
err();
}
}
function sendEmail(campground) {
return new Promise((resolve, reject) => {
Reservation.app.models.Email.send(formEmailObject(campground),
function(
err,
mail
) {
if (err) {
console.log(err);
Raven.captureException(err);
reject(err);
} else {
console.log(mail);
console.log("email sent!");
resolve(mail);
}
});
});
}
function formEmailObject(campground) {
return {
to: "loopbackintern#yopmail.com",
from: "noreply#optis.be",
subject: "Thank you for your reservation at " + campground.name,
html:
"<p>We confirm your reservation for <strong>" +
campground.name +
"</strong></p>"
};
}
Reservation.validate("startDate", dateValidator, {
message: "endDate should be after startDate"
});
Reservation.observe("after save", async function(ctx, next) {
try {
const campground = await Reservation.app.models.Campground.findById(
ctx.instance.campgroundId
);
const mail = await sendEmail(campground);
next();
} catch (e) {
Raven.captureException(e);
next(e);
}
});
};
Sorry for the poor formatting. When the flow is done I get this error:
(node:3907) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Callback was already called.
I am calling the next() callback in two places, one in the try code and one in the catch code. I assume that when it all goes right, next callback is called only once, and the same when it goes wrong. But it seems that it is called twice and I don't know why.
I also tried to call next outside the try/catch code but it results in the same error. If I left only the next that is called inside the catch code it doesn't throw the error.
Any idea? Thanks!
if you are using async function you shouldn't explicitly call next, it gets automatically called.
check out this github issue for loopback async/await
so your hook can be like the following.
Reservation.observe("after save", async ctx => {
try {
const campground = await Reservation.app.models.Campground.findById(
ctx.instance.campgroundId
);
const mail = await sendEmail(campground);
} catch (e) {
Raven.captureException(e);
throw e;
}
});
NB: you don't need to wrap it in try catch unless you want to modify/work with the error.
You should declare your sendEmail method as async as it returns a promise.
async function sendEmail(campground) {
...
}
After reading this article, I created a await-handler.js file which include following code.
module.exports = (promise) =>
promise
.then(data => ({
ok: true,
data
}))
.catch(error =>
Promise.resolve({
ok: false,
error
})
);
Then in MyModel.js file, I created a async function to get a value from database as follow.
const awaitHandler = require("./../await-handler.js")
const getMaxNumber = async (MyModel) => {
let result = await awaitHandler(MyModel.find());
if (result.ok) {
if (result.data.length) {
return result.data.reduce((max, b) => Math.max(max, b.propertyName), result.data[0] && result.data[0].propertyName);
} else {
return 0;
}
} else {
return result.error;
}
}
As per #Mehari's answer, I've commented call to next() method as follow:-
module.exports = function(MyModel) {
MyModel.observe('before save', async(ctx, next) => {
const maxNumber = await getMaxNumber (MyModel);
if(ctx.instance) {
...
set the required property using ctx.instance.*
like createdAt, createdBy properties
...
// return next();
} else {
...
code for patch
...
// return next();
}
})
}
This solves the warning issue whenever saving endpoint is triggered.
But the warning issue still appear when I run the endpoint to load the resource.Like
http://localhost:3000/api/MyModel
Previously, the issue appear only when the before save operation hook gets triggered.
After encountering this issue, I checked adding access and loaded operation hooks and I found that the the warnings are issued after loaded operation hook.
MyModel.observe('access', (ctx, next) => {
return next();
})
MyModel.observe('loaded', (ctx, next) => {
return next();
})
What could have caused this issue and how can it gets resolved?
I send data from my input fields to my api:
$.ajax({
url: '/api/login',
type: 'GET',
dataType: 'json',
ContentType: 'application/json',
data: {formData},
success: (data) => {
console.log('SUCCESS')
console.log(data)
this.setState({
isInProcess: false
})
},
error: (jqXHR) => {
console.log(jqXHR)
console.log('ERROR')
this.setState({isInProcess: false})
}
})
on my server-side I have a function to see if I have required user in db:
async function findUser(data) {
try {
const user = await User.findOne({email: data.email,
password: data.password})
console.log('User was found')
return { user }
} catch (err) {
console.log('error', err)
throw err
}
}
which will be executed here:
app.get('/api/login', async (req, res) => {
const data = req.query
try {
const foundUserData = await findUser(data.formData)
return res.json(foundUserData)
} catch (err) {
return res.status(400).json(err)
}
})
It works fine, but if a user wasn't found in db i sends success anyway. Why?
await findUser(data.formData) won't throw error, return either null or user object. You may check something following
app.get('/api/login', async (req, res) => {
const data = req.query
try {
const foundUserData = await findUser(data.formData)
if(foundUserData && foundUserData.user) {
return res.json(foundUserData)
} else {
return res.status(400).json({message: 'User not found'});
}
} catch (err) {
return res.status(500).json(err)
}
})
It sends success because none of your queries error'ed, just because it didn't find anything does not mean that the query failed because it obviously succeeded in finding out if what ever you're looking for exists or not.
To send an error in case of not found you need to check if response is empty in which case you want to send error
When no user is find you get a null value. You may try to put more logic on your success parameter with that for example:
success: function (data) {
if(!!data && data != null) {
alert('Success');
} else {
alert('No data');
}
}