I have a rest api that receives a patch request with the following possible parameters:
firstname
lastname
image
birthday
If I receive the image parameter I will need to upload the image to a cdn and after that to update and save the user profile.
If I don't receive the image parameter I will update and save the user profile.
What I had in mind was:
if photo then upload -> promise -> update fields -> save
else update fields -> save
My question: is there a best practice so I don't need to double a portion of code for each case?
app.patch('/users', authenticate, (req, res) => {
let body = _.pick(req.body, ['birthday', 'country', 'city', 'gender', 'firstname', 'lastname', 'photo']);
if ( body.photo ){
cloudinary.uploader.upload(body.photo,function(error, result) {
if( error ) {
res.status(400).send(error);
}
body.photo = result.url;
req.user = { ... req.user, ... body};
console.log ('update profile');
res.send(req.user);
});
}
req.user = { ... req.user, ... body};
console.log ('update profile');
res.send(req.user);
});
I have assumed that there is a createUser function that return a promise.
let userData = req.body;
userData.photo = undefined;
return Promise.resolve()
.then(function() {
if (req.body.photo) {
return promisifiedUpload(body.photo);
} else {
return undefined;
}
})
.then(function(result) {
if (result && result.url) {
userData.photo = result.url;
}
return createUser(userData);
})
.then(function(it) {
res.send(it);
})
.catch(function(err) {
res.status(400).send(err);
});
There are plenty of helper libraries that help you turn functions that work with node-style callbacks like function(err, resp){...} to promises, but for the sake of clarity this is how you could use one:
function promisifiedUpload(url) {
return new Promise(function(resolve, reject) {
cloudinary.uploader.uproad(url, function(err, resp) {
if (err) {
reject(err);
} else {
resolve(resp);
}
});
});
}
Some material that might come in handy:
ecma6 promises: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
npm util.promisify https://nodejs.org/api/util.html#util_util_promisify_original
How do I convert an existing callback API to promises?
Related
I'm trying to store Redis key value to a variable in nodejs, something like
let gPost;
redis.get("posts", async function (err, post) {
if (err) console.log(err);
if (post) gPost = post;
}
but this approach is giving me undefined. Is there any way by which I can store value to Redis? I've already searched for it and a few posts suggested using callbacks. But what I basically want is something like this:
router.post("/:id/likes", async (req, res) => {
try {
redis.get(`posts.${req.params.id}.likes`, function (err, likeCount) {
if (err) console.error(err.message);
redis.get(`posts.${req.params.id}`, async function (err, post) {
if (err) console.log(err);
if (post) {
await customCallback(likeCount, post, req, res);
const retPost = JSON.parse(post);
return res.send({ retPost, redis: true });
} else {
try {
const reqPost = await Post.findById(req.params.id).lean().exec();
redis.set(`posts.${req.params.id}`, JSON.stringify(reqPost));
await customCallback(likeCount, reqPost, req, res);
const retPost = JSON.parse(post);
return res.send({ retPost, redis: false });
} catch (err) {
console.log(err);
}
}
});
console.log(upPost);
});
} catch (err) {
return res.status(500).send({ message: err.message });
}
});
So, here I want to increase my likes count on a post. But I don't want to hit any unnecessary requests to the database. Here first I'm getting posts.id.likes and inside it, I'm trying to fetch that post. If a post is found I'll increase my likes there only. Else, I'll make an API call to the database to fetch that post. Can you where I'm getting it wrong, or any other efficient approach I can use? Thanks.
If you're using a recent version of node-redis, you can just use promises.
Your code seems to simplify to something like
/**
* Get a post from Redis or the database.
*
* If found in the database, caches it in Redis.
*
* Returns a pair: post object and whether it was from Redis.
* #param id Post ID.
*/
async function getPost(id) {
const redisKey = `posts.${id}`;
const postData = await redis.get(redisKey);
if (postData) {
return [JSON.parse(postData), true];
}
const postObj = await Post.findById(id).lean().exec();
await redis.set(redisKey, JSON.stringify(post));
return [postObj, false];
}
router.post("/:id/likes", async (req, res) => {
try {
const { id } = req.params;
const [postObj, fromRedis] = await getPost(id);
const likeCount = await redis.get(`posts.${id}.likes`);
await customCallback(likeCount, postObj, req, res);
return res.send({ postObj, redis: fromRedis });
} catch (err) {
return res.status(500).send({ message: err.message });
}
});
I'm fairly new to nodejs and have stumbled into a problem with my code.
The documentation for SQL Server and a guide I found on Youtube both handle their code this way, but after starting to use bycrypt I've noticed my function ends after the request is complete although I'm using .then().
Anyways, here's my code so far:
router.post('/login', (req, res) => {
getLoginDetails(req.body.username, req.body.password).then(result => {
console.log(result);
res.json(result);
})
});
async function getLoginDetails(username, password) {
await pool1Connect;
try {
const request = pool1.request();
request.input('username', sql.NVarChar, username);
request.query('SELECT * FROM users WHERE username = #username', (err, result) => {
if (err) {
return ({err: err})
}
if (result.recordset.length > 0) {
bcrypt.compare(password, result.recordset[0].user_password, (err, response) => {
if (response) {
console.log(result.recordset);
return(result.recordset);
} else {
return({message: "Wrong password or username!"})
}
})
return(result)
} else {
return({message: "user not found!"})
}
})
} catch (err) {
return err;
}
}
I tried logging both the request and the return value from the function getLoginDetails and the request log came faster, so I assume it's not waiting for the program to actually finish and I can't figure out why...
Sorry if that's obvious, but I'd love to get some help here!
EDIT:
router.post('/login', async (req, res) => {
// res.send(getLoginDetails(req.body.username, req.body.password))
await pool1Connect
try {
const request = pool1.request();
request.input('username', sql.NVarChar, req.body.username);
request.query('SELECT * FROM users WHERE username = #username', (err, result) => {
console.log(result);
bcrypt.compare(req.body.password, result.recordset[0].user_password, (err, response) => {
if (response) {
res.send(result);
} else {
res.send('wrong password')
}
})
//res.send(result)
})
} catch (err) {
res.send(err);
}
});
This code works, but when I tried to encapsulate it in a function it still didn't work.
#Anatoly mentioned .query not finishing in time which makes sense, but I thought mssql .query is an async function?
Your problem arises from an wrong assumption that callbacks and promises are alike, but on the contrary callbacks don't "respect" promise/async constructs
When the program hits the bottom of getLoginDetails the progrm execution has already split into 2 branches one branch returned you the (empty) result whereas the other one still busy with crypto operations.
Though it is true that an async function always returns a promise but that doesn't cover any future callbacks that might execute inside it. As soon as node reaches the end of function or any return statement the async function's promise get resolved(therefore future callbacks are meaningless), what you can do instead is handroll your own promise which encampasses the callbacks as well
router.post('/login', (req, res) => {
getLoginDetails(req.body.username, req.body.password))
.then((result)=>{
res.send(result);
})
.catch((err)=>{
res.send(err);
})
});
async function getLoginDetails(username, password) {
await pool1Connect
return new Promise( (resolve,reject) => {
try {
const request = pool1.request();
request.input('username', sql.NVarChar, username);
request.query('SELECT * FROM users WHERE username = #username', (err, result) => {
console.log(result);
bcrypt.compare(password, result.recordset[0].user_password, (err, response) => {
if (response) {
resolve(result);
} else {
resolve('wrong password')
}
})
})
} catch (err) {
reject(err);
}
});
}
You didn't return any result to getLoginDetails. Either you use async versions of request.query and bcrypt.compare (if any) or wrap request.query to new Promise((resolve, reject) like this:
const asyncResult = new Promise((resolve, reject) => {
request.query('SELECT ...
...
if (err) {
resolve({err: err}) // replace all return statements with resolve calls
}
...
})
const queryResult = await asyncResult;
I want to refactor code for chain of promises by async, await. I have sequelize ORM for DB management and the code is written in AWS Lambda function having multiple middleware. In such cases I have to traverse code for multiple entries using sequelize transactions. It is easy to manage using promise.all() but need to change it to async await syntax for cleaner code.
Here are my demo code.
/* get all invoice where user_id === current logged in user, and where status != "paid" */
db.Invoice.findAll({
where: {
user_id: currentLoggedInUser,
status: {
$ne: "paid"
}
}
}).then(invoices => {
if (!invoices || invoices === null) {
return false;
}
function addScheduledTransactionAttempts(invoice, tryPayOnDate, t) {
return new Promise((resolve, reject) => {
/* check If any ScheduledTransactionAttempts exists for this invoice.id */
db.ScheduledTransactionAttempts.find({
where: {
invoice_id: invoice.id
}
})
.then(function(attempts) {
if (attempts) {
attempts
.destroy({}, {
transaction: t
})
.then(deletedAttempts => {
console.log("Attempts Record Deleted: ", deletedAttempts);
})
.catch(error => {
reject(error);
t.rollback();
});
}
return db.ScheduledTransactionAttempts.create({
invoice_id: invoice.id,
payment_source_id: PaymentMethodId,
try_pay_on_date: tryPayOnDate,
stripe_customer_id: currentLogInStripeCustomerId
}, {
transaction: t
})
.then(function(attempt) {
resolve(attempt.id);
})
.catch(error => {
reject(error);
t.rollback();
});
})
.catch(error => {
reject(error);
t.rollback();
});
});
}
//Run transaction to addScheduledTransactionAttempts
return db.sequelize.transaction().then(function(t) {
let promiseArr = [];
var i = 0;
invoices.forEach(function(invoice) {
var schedulePaymentDate = moment(paymentDate);
if (invoice) {
let tryPayOnDate = schedulePaymentDate
.add(i, "month")
.format("YYYY-MM-DD");
promiseArr.push(
addScheduledTransactionAttempts(invoice, tryPayOnDate, t) //calling above function
);
i++;
}
});
//now execute promise all
Promise.all(promiseArr)
.then(function(result) {
t.commit();
return true;
})
.catch(function(err) {
t.rollback();
return false;
});
});
});
In the above code I want to change
Promise.all(promiseArr)
which is calling
addScheduledTransactionAttempts
function to do DB queries to simple async function await process to make it easy simpler understandable without having multiple .then or .then inside then promises.
Any help regarding would be appreciated,
Thanks.
It's quite simple. await is valid when invoking methods that return a Promise. All of your SDK methods already return a promise, so refactoring should be quite straight forward. Here's something to get you off ground:
const processInvoices = async currentLoggedInUser {
const invoices = await db.Invoice.findAll({
where: {
user_id: currentLoggedInUser,
status: {
$ne: 'paid',
},
},
});
if (yourOwnLogicForInvoicesObject) {
for (const invoice of invoices) {
const potentiallyFoundInvoice = await db.ScheduledTransactionAttempts.find({
where: {
invoice_id: invoice.id,
},
});
if (potentiallyFoundInvoice) {
await addScheduledTransactionAttempts(potentiallyFoundInvoice)
}
}
}
}
const addScheduledTransactionAttempts = async invoice => {
console.log('Do something with your invoice', invoice)
}
Long story short: refactor the code inside your functions into smaller functions and make these new functions async, just like I did with addScheduledTransactionAttempts and processInvoices
More on async/await
I've been reading some CRUD / Mongoose guides, but haven't a good explainer for conditionally updating fields.
So for example, an action called updateItem is used in one place to update item.price but in another place it updates item.color. Does anyone know a good explanation or tutorial for Mongoose CRUD APIs that shows this?
I'm getting the blow code to work fine, but I have a feeling it could be cleaner :)
Thanks!!!
router.put('/tasks/:id', (req, res) => {
Task.findByIdAndUpdate(req.params.id,
req.body.owner ? { owner: req.body.owner } : { hours: req.body.hours }, { new: true })
.then(task => {
res.status(201).json(task)
})
.catch(err => {
console.log('Our error', err)
})
});
Another approach you could take is to first retrieve the object, and then only update the value if it is passed into the put request. An example of that could be something like this:
router.put('/tasks/:id', (req, res) => {
let price = req.body.price;
let color = req.body.color;
Task.findById(req.params.id, function (err, task) {
if (err) return handleError(err);
task.color = color || task.color;
task.price = price || task.price;
task.save(function(err, updatedTask) {
if err return handleError(err);
return res.send(updatedTask);
});
});
});
Here's another cleaner approach using async-await functions:
// Import promisify from utils
const promisify = require('utils').promisify;
// Wrap findByIdAndUpdate into a promise
const updateOwnerPromise = promisify(Task.findByIdAndUpdate);
// Write an async handler now
updateOwnerPromiseAsync = async (req, res) => {
const replacementObject = req.body.owner ? { owner: req.body.owner } : { hours: req.body.hours };
try {
await updateOwnerPromise(replacementObject, { new:true} );
return res.status(200).send({ message: 'Owner updated successfully!' });
} catch(err) {
// TODO: handle error here
console.log('Our error', err)
return res.status(500).send({ message: 'Failed to update owner, because of some issue at the server!' });
}
}
// Modify the express route with the handler
router.put('/tasks/:id', updateOwnerPromiseAsync);
I don't have much experience in async code and I'm stuck with a problem, here is my code :
if (!req.params.user_name) {
req.params.user = req.me;
} else {
User.findOne({username: req.params.user_name}, '_id', (error, user) => {
if (error) {
return next(error);
}
if (!user) {
let error = new Error('User not found');
return next(error);
}
req.params.user = user;
});
}
Account.findOne({name: req.params.account_name, created_by: req.params.user})
.populate(['currency', 'created_by'])
.exec((err, account) => {
if (err) {
return next(err);
}
return res.send(account);
});
As you can see the problem is that in one case I just have a simple procedural action to do, in the other I have to query the database which is async, then I have to execute the code below. I can't just simply put the Account query in the callback of the User query because I don't need to execute User query all the time.
I've tried to find an answer here but the only results I've found are about executing one async task or another (ex: Working with promises inside an if/else).
Following the recommandations on this post I've thought about wrapping the code inside the if block in an anonymous function and do something like:
let get_user;
if (!req.params.user_name) {
let get_user = () => {req.params.user = req.me};
} else {
let get_user = User.findOne({username: req.params.user_name}, '_id', (error, user) => {
if (error) {
return next(error);
}
if (!user) {
let error = new Error('User not found');
return next(error);
}
req.params.user = user;
});
}
get_user().then(() => {
Account.findOne({name: req.params.account_name, created_by: req.params.user})
.populate(['currency', 'created_by'])
.exec((err, account) => {
if (err) {
return next(err);
}
return res.send(account);
});
});
But I find it weird and I guess I would need to return a Promise from the anonymous function in the if block.
So what would be an elegant way of solving this ? I'm learning node.js on my free time and any general suggestions would be greatly appreciated.
Thanks for your time.
Callbacks shouldn't be used with Mongoose; it has been supporting promises for a long time.
This
let get_user = () => {req.params.user = req.me};
won't work because get_user is expected to return a promise.
It's usually done like:
let userPromise;
if (!req.params.user_name) {
userPromise = Promise.resolve(req.me);
} else {
userPromise = User.findOne(...);
}
userPromise().then((user => {
req.params.user = user;
return Account.findOne(...);
});
Notice that req.params.user is common code for both conditions. This is conveniently done with async..await, which is syntactic sugar for promises:
try {
let user;
if (!req.params.user_name) {
user = req.me;
} else {
user = await User.findOne({username: req.params.user_name}, '_id');
}
req.params.user = user;
const account = await Account.findOne({name: req.params.account_name, created_by: req.params.user})
.populate(['currency', 'created_by'])
.exec();
res.send(account);
} catch (err) {
next(err);
}
This is supposed to be a body of async middleware function, which wasn't shown in original code.
As explained in this answer, Express doesn't support promises itself, all async middlewares and route handlers should be wrapped with try..catch for error handling.