I started to learn ES6 and I'm transforming my project from ES5 to ES6. I want to ask if it's sense to use async/await in middlewares ? How to use it in this example :
middlewareObj.checkCampground = (req,res,next) =>{
if(req.isAuthenticated()){
Campground.findById(req.params.id, (err, foundCampground) =>{
if(err || !foundCampground){
req.flash("error", "Campground not found");
res.redirect("back");
} else {
if(foundCampground.author.id.equals(req.user._id) || req.user.isAdmin){
next();
} else {
req.flash("error", "You don't have permission to do that");
res.redirect("back");
}
}
});
} else {
req.flash("error", "You need to be logged in to do that");
res.redirect("back");
}
};
When you only have a single asynchronous operation like you do here, you don't gain much (if anything) from switching to await. The bigger benefits come when you need to sequence multiple asynchronous operations and perhaps even have some branching. Then await lets you write much simpler code.
Plus, most of your code here is really just about checking results and getting the right error message back to the user and that doesn't get a lot simpler with await as it's just a bunch of rote checks either way.
Here's an implementation that also attempts to use exceptions to consolidate all the error returns so you don't have as many places where you're doing req.flash() and res.redirect():
middlewareObj.checkCampground = async (req,res,next) => {
try {
if(req.isAuthenticated()) {
throw new Error("You need to be logged in to do that");
}
const foundCampground = await Campground.findById(req.params.id);
if (!foundCampground) {
throw new Error("Campgound not found")
}
if (foundCampground.author.id.equals(req.user._id) || req.user.isAdmin) {
next();
} else {
throw new Error("You don't have permission to do that");
}
} catch(e) {
console.log(e);
req.flash(e.message);
res.redirect("back");
}
};
Here's another alternative without async/await that just attempts to consolidate the error handling a bit. You can't get around the fact that there are three if checks and four possible errors:
middlewareObj.checkCampground = (req,res,next) => {
function error(msg, location = "back") {
req.flash(msg);
res.redirect(location);
}
if(req.isAuthenticated()) {
error("You need to be logged in to do that");
return;
}
Campground.findById(req.params.id).then(foundCampground => {
if (!foundCampground) {
error("Campground not found");
} else if (foundCampground.author.id.equals(req.user._id) || req.user.isAdmin) {
next();
} else {
error("You don't have permission to do that");
}
}).catch(err => {
console.log(err);
error("Database Error - Campground not found");
});
};
Note that in both of these, I make sure and log an actual database error if there is one.
Related
I have a backend method called LikeExists() to verify if a certain user has liked a certain post.
public async Task<bool> LikeExists(int postId)
{
var post = await _postRepository.GetPostByIdAsync(postId);
var user = await _userRepository.GetUserByUsernameAsync(User.GetUsername());
if (_context.Likes.Where(i => i.PostId == post.Id && i.UserId == user.Id).FirstOrDefault() != null) return true;
return false;
}
The method works fine in Postman, but it does not do the job in Angular. If a user presses a like button I first want to check if this user has already liked this post. If he has, he will unlike it and the like will be deleted from the database. If he hasn't liked it, he will like it and the like will be saved in the database.
likeExists(){
this.postService.likeExists(this.post.id).subscribe((response: boolean) =>{
this.like = response;
});
}
likePost() {
if(this.likeExists){
this.postService.likePost(this.post.id, this.model).subscribe((response: Like) => {
this.likee = response;
console.log(response);
this.toastr.success('Liked');
}, error => {
console.log(error);
this.toastr.error(error.error);
});
} else {
this.postService.deleteLike(this.post.id).subscribe(() => {
this.toastr.success('Unliked');
}, error => {
console.log(error);
})
}
}
The problem is it always enters the if{} clause and never the else{} clause. The method below returns an Observable. I think the problem is that it must return a boolean. How can I make this work?
This is the method in the postService:
likeExists(postId: number) {
return this.http.get(this.baseUrl + 'like/exists/' + postId);
}
Try avoiding nested subscription since it will result in an unreadable and hard to maintain code, use rxjs pipes with operators instead, try something like this:
let id= this.post.id;
let likeExists$ = this.postService.likeExists(id);
likeExists$
.pipe(
switchMap(likeExists => {
if (likeExists) {
// delete like
return this.postService.deleteLike(id);
}
// otherwise addlike
return this.postService.addLike(id);
})
).subscribe(
res=> this.toastr.success('Success'),
err=> this.toastr.error('Failed')
);
or even shorter
let id= this.post.id;
let likeExists$ = this.postService.likeExists(id);
likeExists$
.pipe(switchMap(
liked => liked ? this.postService.deleteLike(id) : this.postService.addLike(id)}))
.subscribe(
res=> this.toastr.success('Success'),
err=> this.toastr.error('Failed')
);
The problem is that this.http.get is asynchronous, which means that likeExists returns before this.like is being set. You need to wait for the value to be returned in your observable. Refactor your code to something along these lines:
likePost() {
// Check to see if like exists and wait for response from server
this.postService.likeExists(this.post.id).subscribe((response: boolean) => {
this.like = response;
if (this.like) {
this.postService.likePost(this.post.id, this.model).subscribe((response: Like) => {
this.likee = response;
console.log(response);
this.toastr.success('Liked');
}, error => {
console.log(error);
this.toastr.error(error.error);
});
} else {
this.postService.deleteLike(this.post.id).subscribe(() => {
this.toastr.success('Unliked');
}, error => {
console.log(error);
})
}
});
}
Also, this is a nice guide to asynchronous concepts in general. And the RxJS docs have a bunch of helpful information to get started.
in socket.io i am trying to check if a user exist easily enough to where i can just call
if(checkUserExist(uid) == 'true'){
success();
}else{
failure();
};
so i figured out i need to use promises because the function i use to get info from the database is async so i do this
function checkUserExist(uid){
return new Promise(resolve => {
webUser.findOne({ _id: uid }, function(err, uid) {
if(uid){
console.log("USER EXISTS")
resolve('true')
}if(!uid){
console.log("USER NO REAL")
resolve('false')
}
})
});
and when i'm trying to use the function like this
socket.on('getAgents',function(uid){
console.log(checkUserExist(uid))
if(checkUserExist(uid) == 'true'){
console.log('user does exist getting agents')
agentList.find({}, function(err, docs) {
docs.forEach(function(d) {
socket.emit('newAgent', d.agentName)
});
});
}else if(checkUserExist(uid) == 'false'){
console.log('invalid uid ' + uid)
socket.emit('serverError', 'Invalid UID '+ uid)
}
})
the returned value is Promise { <pending> }
i am not sure what to do i thought that it was a simple enough task but obviously i don't yet know how to do it. is there anybody out there that can help me out.
promises is a fairly new concept to me and i still don't fully understand how they work should maybe use a library like promisify?
Thanks a ton :)
So, checkUserExist() returns a promise, not a value. That's how you coded it!
And, since the value is obtained asynchronously, you can't return the value directly anyway. See the canonical answer on that issue for more explanation on that topic.
To make your code work properly, you would have to actually use the promise that your function returns:
socket.on('getAgents',function(uid){
console.log(checkUserExist(uid))
checkUserExist(uid).then(result => {
if (result) {
agentList.find({}, function(err, docs) {
if (err) {
// decide what to do if there's a DB error here
return;
}
docs.forEach(function(d) {
socket.emit('newAgent', d.agentName)
});
});
} else {
socket.emit('serverError', 'Invalid UID ' + uid)
}
}).catch(err => {
socket.emit('serverError', 'Error processing UID ' + uid);
});
});
I'm trying to implement the following scenario:
1. Client calls a meteor-method.
2. Inside the meteor-method i make an HTTP-Post to a different server.
3. When the HTTP-Call is responded, the meteor method should return true and in the case an error occurs it should return false.
Here is what my meteor method looks like:
uploadUserImage: function(data_url,userid) {
asyncfnc =function(data,uid){
HTTP.post("http://localhost:2000/upload", {
data: {
"data_url": data,
"user_id": uid
}
},function(err,res){
console.log(res);
if (err){
console.log("error");
throw new Error(err.message);
}
else{
console.log("return true");
return true;
}
});
};
var waitForResult = Meteor.wrapAsync(asyncfnc);
var result = waitForResult(data_url,userid);
return result;
}
The HTTP-Call works and I also get into the Callback of the HTTP.post-function.
But on the clientside where I called the meteor-method i don't get into my callback-function. It looks like this:
Meteor.call("uploadUserImage",data_url,Session.get("newUserID"),function (err, res) {
if(err){
console.log(err);
} else {
console.log('response: ', res);
}
});
What am I doing wrong? Why is my meteor-method not returning anything?
Is everything correct with my Meteor.wrapAsync()?
Thanks for your help!
I found a solution, which does not require Meteor.wrapAsync().
var url = "http://localhost:2000/upload";
//synchronous GET
var result = Meteor.http.post(url,{
data: {
"title": "i want to upload a picture",
"data_url": data_url,
"user_id": userid
},timeout:30000});
if(result.statusCode==200) {
console.log(result);
console.log("response received.");
return result;
} else {
console.log("Response issue: ", result.statusCode);
var errorJson = JSON.parse(result.content);
throw new Meteor.Error(result.statusCode, errorJson.error);
}
This makes the HTTP-Post-Call synchronous, so there is no need to wrap async.
You are asking too much in this situation.
Meteor methods can be called synchronously, but it's not advisable if the method is doing a remote call like this.
My feeling is that you are hanging on to a procedural programming model where you want a synchronous result to 1) a call to your server, and 2) a request sent to another remote server. And you want to get a return value from your call. It doesn't work like that.
Meteor protects you to a large degree from dealing with asynchronicity, but sometimes you have to accept that a little more work is required to deal with it correctly.
So my recommendation is to use callbacks for notification.
My meteor code goes a couple Meteor.call methods deep at some points. If I have an error in the 2nd layer and I want to throw that meteor error back to the client side how can I do that?
Currently I have something like this, but I'm getting very confusing outputs and I don't think I fully understand what is happening when I'm calling throw new Meteor.Error(500, e.category_code, e.description);
In client.js
Meteor.call('firstCall', data, function (error, result) {
if(result) {
doSomething();
}
else{
console.log(error);//just shows 500
}
});
In server.js
var Future = Meteor.npmRequire("fibers/future");
function extractFromPromise(promise) {
var fut = new Future();
promise.then(function (result) {
fut.return(result);
}, function (error) {
console.log(error);
fut.throw(error);
});
return fut.wait();
}
firstCall: function (data){
try{
Meteor.call('secondCall', data, 'http://testhref.com/test', 'http://testhref2.com/test' function (error, result) {
return result;
});
}
catch(e){
throw new Meteor.Error(500, e.category_code, e.description);
}
}
secondCall: function (data, paymentHref, otherHref){
try{
var associate = extractFromPromise(balanced.get(paymentHref).associate_to_customer(otherHref).debit({
"amount": data.paymentInformation[0].total_amount * 100,
"appears_on_statement_as": "Trash Mountain"}));
}
catch(e){
Collection.update(data.id, {
$set: {
'failed.category_code': e.category_code,
'failed.description': e.description
}
});
throw new Meteor.Error(500, e.category_code, e.description);
}
}
In your case, the catch in firstCall is not going to have anything defined for e.category_code and e.description when secondCall throws. This is because in secondCall you are passing these two as arguments to Meteor.Error, which takes as its arguments error, reason, and details:
https://github.com/meteor/meteor/blob/devel/packages/meteor/errors.js
In order to pass these through, you will need to amend firstCall to use these properties:
firstCall: function (data){
try{
Meteor.call('secondCall', data, 'http://testhref.com/test', 'http://testhref2.com/test');
}
catch(e){
throw new Meteor.Error(500, e.reason, e.details);
}
}
I'm not even sure you need to split it up into two calls for modularity, as you can just use normal Javascript functions. But we can discuss that elsewhere.
I few things to mention here:
Async function don't throw exceptions (except you make them kind of sync using Meteor._wrapAsync as I will explain later), they return the error on another way (as the first argument in NodeJS callback-style). This applies both for Meteor.call and to your doSomeAsyncThing.
I can't see the benefit of using Meteor.call on the server. Meteor.call is meant to call server methods from the client. In this case you could just call YourObj.secondCall from inside of firstCall.
Returning something from inside of a callback (as you are doing inside firstCall) doesn't have any effect. You want your async code to work as sync code, so I suggest using Meteor._wrapAsync which is very well explained here.
So, I would implement server side a bit different:
firstCall: function (data){
try{
return this.secondCall(data);
}
catch(e){
throw new Meteor.Error(500, e.category_code, e.description);
}
secondCall: function (data){
try{
return Meteor._wrapAsync(doSomeAsyncThing)(data);
}
catch(e){
Collection.update(data.id, {
$set: {
'failed.category_code': e.category_code,
'failed.description': e.description
}
});
throw new Meteor.Error(500, e.category_code, e.description);
}
Hope this helps!
I'm building a messenger application, and before a conversation is created I want to verify if a user exists. If it does, then it will create the conversation. If not, then it should return an error. I've been working with this code on the server side but for some reason it won't work. I've tried many different tweaks, but this is basically my structure:
Meteor.methods({
createConversation: function(secondPerson) {
function doesUserExist(secondPerson) {
var userx = Meteor.users.findOne({username: secondPerson});
if (userx === secondPerson) {
return false;
} else {
return true;
}
}
if (doesUserExist()) {
Conversations.insert({
person1: Meteor.user().username,
person2: secondPerson
});
} else {
Conversations.insert({
person1: "didn't work"
});
}
}
});
The main point you were missing is that find returns a cursor, whereas findOne returns a document. Here is one way to implement the method:
Meteor.methods({
createConversation: function(username) {
check(username, String);
if (!this.userId) {
throw new Meteor.Error(401, 'you must be logged in!');
}
if (Meteor.users.findOne({username: username})) {
return Conversations.insert({
person1: Meteor.user().username,
person2: username
});
} else {
throw new Meteor.Error(403, username + " does not exist!");
}
}
});
Note the following features:
validates that username is a string
requires that the user be logged in to create a conversation
reduces the user existence check to a single line
returns the id of the new conversation
uses Meteor.Error with explanations which can be seen on the client
To use it just open your browser console and try making calls like:
Meteor.call('createConversation', 'dweldon', function(err, id){console.log(err, id);});