Async/await while performing CRUD operations in mongoose - javascript

I am performing a delete operation using findByIdAndRemove mongoose query method and want to make sure that the deleted doc gets returned. I googled it and found that we pass a callback to findByIdAndRemove in which we pass err, doc args and if delete is successful the deleted value gets returned in doc which we can return from the fn call. But I am somewhat confused in async and await. I have deleteUserTask fn which internally calls async deleteUserTask fn which is promise based and expects a promise in return which when resolved (.then) I can get the value of the task which was deleted.
I am not sure how to return the value of doc from my async deleteUserTask fn which will be then resolved in .then promise and I can use it?
With below approach the value returned is null as I am not returning the doc value at all.
function deleteUserTask(req, res, next) {
taskService
.deleteUserTask(req.params.taskId)
.then(task => {
console.log(
"Delete task request took " + (moment.now() - req.requestTime + " ms")
);
// below gives null
console.log(task);
res.json({ task });
})
.catch(err => next(err));
}
taskService.js
async function deleteUserTask(id) {
return await Task.findByIdAndRemove(id, function(err, doc) {
if (err) {
return err;
}
});
}

A callback shouldn't be used with findByIdAndRemove in case promises are used. It should be:
async function deleteUserTask(id) {
return await Task.findByIdAndRemove(id);
}
At this point there are no benefits from using async function, it could be simplified to:
function deleteUserTask(id) {
return Task.findByIdAndRemove(id);
}

Related

Using async/await with callback functions

I'm trying to build a service layer on top of mongo. I have a User object which has an array of referenced Achievements.
After I've authenticated the user by JWT or some other means, I enter the service layer and query the relationship as follows.
findForUser(userId: string | Types.ObjectId): Promise<Achievement[]> {
return new Promise((resolve) => {
UserSchema.findOne({ _id: userId },
async (err: any, user: User) => {
const AchievementMap: Achievement[] = [];
if (err) throw new Error(err);
user.achievements?.forEach((a) => {
// #ts-ignore
AchievementMap.push(a);
});
resolve(AchievementMap);
});
});
}
What is the async/await approach to returning the result of the callback method passed into UserSchema.findOne?
findOne returns an awaitable object - so you don't need to pass a callback to it. Don't try mixing callbacks with async/await. The only way to return the value from the callback, as a result of the constructed promise is by using resolve (only available in the promise executor).
Instead, make it all async - functionally similar but far cleaner.
async findForUser(userId: string | Types.ObjectId): Promise<Achievement[]> {
const user = await UserSchema.findOne({ _id: userId });
const AchievementMap: Achievement[] = [];
user.achievements?.forEach(a => {
AchievementMap.push(a);
});
return AchievementMap;
}
What is the async/await approach to returning the result of the callback
The async and await keywords are tools to manage promises. They aren't tools to manage callbacks.
The entire point of the code you have in the question is to wrap a promise based API around a function that deals in callbacks.
Now you have a promise you can await the return value of findForUser.
You can't use async and await instead of creating a promise.

What is the correct way to await fs.readFile from another asynchronous function?

I'm using a combination of Jest and Supertest to test an API endpoint. I'm using the beforeAll() function of Jest to call the endpoint once before a collection of tests. Before I call the endpoint, I'm reading the request body from a file using fs.readFile.
Whatever I try, I cannot seem to await the result of my function that calls fs.readFile. My request is resulting in a 400 response every time, since the function readRequestBody is not being awaited. It seems that the program flow is continuing without awaiting the result and, therefore, sending an empty request body.
Code:
describe("Test POST call " + process.env.ENV, () => {
const url = config.apiURL;
let responseData: request.Response;
beforeAll(async (done) => {
const requestBody = await readRequestBody();
responseData = await request(config.apiURL)
.post("/v1.0/subjects/courses")
.send(requestBody)
.accept("application/vnd.api+json")
.set("content-type", "application/vnd.api+json");
done();
});
test("authorized should return 201 status code", () => {
expect(responseData.status).toBe(201);
});
});
async function readRequestBody() : Promise<string> {
let requestBody: string = "";
fs.readFile("./request.json", "utf8", (err, req) => {
if (err) {
console.log("Error loading request: " + err.message)
}
requestBody = req.replace("{{newCourseUuid}}", uuid.v4());
});
return requestBody;
}
I understand that fs.readFile reads the contents of a file asynchronously, but it looks like I'm not awaiting the results correctly. What am I missing? Is this something related to the fact that beforeAll is, itself, an asynchronous function?
try await fs.promises.readFile('file.txt') instead 👍
https://nodejs.org/api/fs.html#fs_fs_promises_api
async functions implicitly convert their return value to a promise. So your function signature async function readRequestBody() : Promise<string> means readRequestBody will return a Promise to create Promise to read the body. i.e. Promise<Promise<String>>. Instead you need to either remove the async keyword or the Promise from your return value.
Also your implementation of the function is incorrect as it will always return an empty string since fs.readFile is an asynchronous function.
Here is something that might fix both your issues:
function readRequestBody() : Promise<string> {
return new Promise((resolve, reject) => {
fs.readFile("./request.json", "utf8", (err, req) => {
if (err) {
console.log("Error loading request: " + err.message)
reject(err)
}
let requestBody: string = "";
requestBody = req.replace("{{newCourseUuid}}", uuid.v4());
resolve(requestBody)
});
});
}

Why is findOneAndRemove sometimes passing null to doc in the callback?

I am using the following function which is supposed to have a callback input:
https://mongoosejs.com/docs/api.html#model_Model.findOneAndRemove
I have the following Mutation object that is supposed to delete a particular medicine from the database, and then remove all of the instances of its medicineId from the arrays of all of the Day entries that contain the id.
deleteMedicine: {
type: MedicineType,
args: {
id: { type: new GraphQLNonNull(GraphQLID) }
},
async resolve (parent, args) {
let res = Medicine.findOneAndRemove({ _id: args.id }, async (err, doc) => {
if (err === null && doc === null || doc === null) {
return;
} else if (err) {
console.error(err);
return;
} else {
return await Promise.all(doc.dayNames.map(dayName => {
return DayOfWeek.findByIdAndUpdate(dayName, { $pull: { medicineIds: doc._id }})
.catch((error1) => console.error(error1));
})).catch((error2) => console.error(error2));
}
});
await res;
return res;
}
}
findOneAndRemove successfully deletes the document that has args.id in the Medicine collection, but when It calls the callback function, sometimes it passes null to doc and so the callback function doesn't consistently execute properly.
Also I am getting an unhandled error warning even though I am catching all errors. \
I added logic to handle when err and doc are both null according to this post:
https://github.com/Automattic/mongoose/issues/5022
You should only uses Promises, not callbacks, with GraphQL.js. If the library you're using doesn't support Promises, then you'll need to wrap each callback in a Promise. However, mongoose has great Promise support -- you just omit the callback from your method call and the call will return a Promise.
To avoid "callback hell" when using Promises and issues with properly chaining your Promises, it's generally preferable to utilize async/await syntax. Your cleaned up resolver would look something like:
async resolve (parent, args) {
// Omit the callback to get a Promise and then await the result
const medicine = await Medicine.findOneAndRemove({ _id: args.id })
// If a medicine with that ID couldn't be found, we'll get a null
if (medicine) {
// Make sure we await the Promise returned by Promise.all
await Promise.all(doc.dayNames.map(dayName => DayOfWeek.findByIdAndUpdate(
dayName,
{ $pull: { medicineIds: doc._id } }
))
}
// Make sure we return the appropriate result depending on the field's type
return medicine
}
Typically when dealing with Promises, we use the catch method (or try/catch with async/await) in order to handle errors. However, this is only necessary inside a resolver if you want to mask or otherwise format your error -- otherwise, GraphQL will happily catch any errors for you and return them in the response. However, this only works if your Promises are correctly chained and the resolver returns a Promise. If you end up with an "orphan" Promise that's not awaited, you'll see unhandled error warnings.

How to return value from a promise that's inside another function?

I know that a lot of questions have been asked about async programming and Promises, but I really need an example with this specific code to figure out how I should go about returning promises that's being returned in another function.
I have two functions. The first one is called upon GET'ing to a route. This route should create a payment link and save a booking to a database.
exports.create_booking = function(req, res) {
req.body.payment = exports.create_booking_payment(req.body.total_amount);
console.log(req.body.payment); // This returns ' Promise { <pending> } '
var new_booking = new Booking(req.body);
new_booking.save(function(err, booking) {
if (err)
res.send(err);
res.json(booking);
});
};
However creating the payment link happens with an asynchronous method. My first problem was that I could only access the payment inside the methods callback function.
Now I have wrapped the method inside another (async) method in which a Promise is created and resolved. This method is being returned to my first method with an await statement, but all this returns is: ' Promise { } '.
I know that this happens because the method is being returned before the promise is resolved. But I don't understand why this is. My assumption is that the 'await' statement makes sure to wait returning the method before the async function is completed.
exports.create_booking_payment = async function() {
function asyncPayment() {
return new Promise (function(resolve, reject) {
mollie.payments.create({
amount: 20.00,
description: "Reservation code: ",
redirectUrl: "https://www.website.com/",
webhookUrl: ""
}, function(payment) {
if (payment.error) reject(payment.error)
else { resolve({
id: payment.id,
link: payment.getPaymentUrl(),
status: payment.status
})
}
});
});
}
return await asyncPayment();
}
I hope someone can help me out here...
You seem to have missed that an async function still returns a promise, not the actual value. So, when you call create_booking_payment(), you are getting back a promise that you need to use either .then() with or await with. There's no free lunch across a function boundary. await allows you to program in a synchronous-like fashion inside a function, but still does not allow you to return the value from the function. When it looks like you're returning the value from the async function, you're actually returning a promise that resolves to that value.
So, you'd do this with async and await:
exports.create_booking = async function(req, res) {
try{
req.body.payment = await exports.create_booking_payment(req.body.total_amount);
console.log(req.body.payment);
var new_booking = new Booking(req.body);
new_booking.save(function(err, booking) {
if (err)
res.status(500).send(err);
else
res.json(booking);
});
} catch(e) {
res.status(500).send(err);
}
};
or this with .then():
exports.create_booking = function(req, res) {
exports.create_booking_payment(req.body.total_amount).then(payment => {
console.log(payment);
req.body.payment = payment;
var new_booking = new Booking(req.body);
new_booking.save(function(err, booking) {
if (err)
res.status(500).send(err);
else
res.json(booking);
});
}).catch(err => {
res.status(500).send(err);
});
};
Note, I also added more complete error handling to both scenarios. Also, this code would be a lot cleaner if you "promisfied" or used an already promisified interface for your .save() method. I strongly dislike using plain callback async code inside of promise-based code because it ends up duplicating error handling (like you see in this case).
Also, create_booking_payment() doesn't need to be async or use await since all you need it to do is to return your promise which it already knows how to do:
exports.create_booking_payment = function() {
return new Promise (function(resolve, reject) {
mollie.payments.create({
amount: 20.00,
description: "Reservation code: ",
redirectUrl: "https://www.website.com/",
webhookUrl: ""
}, function(payment) {
if (payment.error) reject(payment.error)
else { resolve({
id: payment.id,
link: payment.getPaymentUrl(),
status: payment.status
})
}
});
});
}

I can't get the value of "result" in Node.js

In my console.log(info), I want to get the value of "result". But when I use console.log(info), I get Promise { <pending> }:
var info=new Promise((resolve, reject) => {
request(options, (err, res, body) => {
if (body.match('success') || body.match('code="25"')) {
resolve("success");
} else {
reject(body);
}
});
}).then(result => {
return result;
}).catch(err => {
console.log("error: " + err)
});
console.log(info);
I would like to get info == result. How can I do it?
Thanks
What happens here
First some explanation of what happens here. When you do something like this:
var info = new Promise((resolve, reject) => {
// ...
}).then(result => {
// ...
}).catch(err => {
// ...
});
then what ends up as the value of the info variable is a promise. That's because you start with a promise returned by the new Promise() constructor, you invoke its .then() method which returns a second promise, and on that second promise you invoke the .catch() method which returns a third promise - and this third promise is what gets saved into the info variable.
All of those method calls return immediately before the original HTTP request is finished so when you reach the next line:
console.log(info);
it's impossible to access the value of the response from the request() call because it didn't happen yet (the request() callback hasn't been called yet at this point). The info variable has a promise which is an object that you can use to register the callbacks that you want run when the value is eventually available. When you pass it to the console.log() it prints that it's a promise.
How to get the value
Now, when you want to print the value when it's finally available then you can attach a promise resolution callback to the promise that you have e.g. with a code like this:
info.then((value) => {
// you can use the value here
console.log('Value:', value);
}).catch((err) => {
// the promise got rejected
console.log('Error:', err);
});
If you are using a relatively recent version of Node (7.0+) then you could use await if you are inside of a function declared with an async keyword, to get the resolution value of the promise like this:
(async function () {
let value = await info;
console.log(value);
})();
or shorter:
(async () => {
console.log(await info);
})();
(If you are already inside of an async function then you don't need the (async () => { ... })(); wrapper.)
How it works
What the await keyword does is it yields the promise from an implicit generator function that passes the control to the outer coroutine control code which attaches a resolution and rejection handlers to that yielded promise and starts the generator again by either returning the resolved value from the await operator or raising an exception if the promise was rejected. The generator can then continue using the return value and catching the exception, or it can let the exception bubble to the outer blocks where it can be caught or converted to a rejection of the implicit promise returned by the async function if not handled along the way.
How you can use it
It may seem complicated but from the point of view of your code it lets you do things like:
let value = await fun();
(where fun() is a function that returns a promise) and have the resolved value of the promise available in the value variable (the real value, not a promise).
Remember that the await keyword throws an exception when the promise in question gets rejected. To handle that case you can use a try/catch block:
try {
let value = await fun();
// promise got resolved, you have value here:
console.log('Value:', value);
} catch (error) {
// promise got rejected, you have error here:
console.log('Error:', error);
}
which is equivalent of this code without the new syntax:
fun().then((value) => {
// promise got resolved, you have value here:
console.log('Value:', value);
}).catch((error) => {
// promise got rejected, you have error here:
console.log('Error:', error);
});
with the main difference being the variable scoping when you await on multiple promises in a single try block as opposed to using multiple chained .then() handlers, each returning a new promise.
Your code looked like it used await but it didn't
I added the example of how to fix your code with await because you wrote your code as if this:
var info = new Promise(...);
// here the 'info' variable is set to a promise
was really this:
var info = await new Promise(...);
// here the 'info' variable is set to the value
// that the promise eventually resolves to
More info
For more info, see:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
Node support
For support of that syntax in Node versions, see:
http://node.green/#ES2017-features-async-functions
In places where you don't have native support for async and await you can use Babel:
https://babeljs.io/docs/plugins/transform-async-to-generator/
or with a slightly different syntax a generator based approach like in co or Bluebird coroutines:
https://www.npmjs.com/package/co
http://bluebirdjs.com/docs/api/promise.coroutine.html
In your code, info is a promise. Thus, you don't compare info to anything. To get access to the result in a promise, you use .then() on the promise as in:
info.then(result => {
// test result here
}).catch(err => {
// handle error here
});
In addition, your .catch() handler is "eating" the error after logging it. If you don't rethrow the error or return a rejected promise from a .catch() handler, then the error is considered "handled" and the promise state changes to fulfilled.
So, you can do this:
let info = new Promise((resolve, reject) => {
request(options, (err, res, body) => {
if (body.match('success') || body.match('code="25"')) {
resolve("success");
} else {
reject(body);
}
});
}).catch(err => {
console.log("error: " + err);
// rethrow error so promise stays rejected
throw err;
});
info.then(result => {
// test result here
}).catch(err => {
// handle error here
});

Categories

Resources