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.
Related
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.
So reading through the Sequelize documentation on Instance and also docs about transactions, the sequelize.transaction() takes autoCallback function as parameter. The docs say:
The callback is called with the transaction object, and should return
a promise. If the promise is resolved, the transaction commits; if the
promise rejects, the transaction rolls back
However I plan to do a lot of things inside on transaction and I want to avoid the callback hell. So I tried to make
try {
let result = sequelize.transaction({
isolationLevel: 'SERIALIZABLE'
}, async t => {
// code to run here with await
let var1 = await Model.find({ transaction: t });
let var2 = await Model.find({ transaction: t });
if (var1.id === 1)
return "Whatever result";
else
throw new Error("Something wicked");
}
)});
// Whatever result
console.log(result);
}catch(e){
// Something wicked
}
This seems to be working perfectly fine. But it is totally undocumented and I havent seen anybody use this. Is it ok, or will I face random issues?
As #Bergi and #Nicholas Tower pointed out in comments:
Since async function ALWAYS returns promise and the callback function should return a promise, this is rather straight-forward answer.
Yes, it can be used.
You are mixing styles, if you want to use straight async/await see below. You are also not passing the transaction (t in your code) to the query, so they won't use it - you have to pass it explicitly in the transaction option. If you make any changes after you should commit the transaction, and subsequently roll it back if there are any errors. You can also improve your code by running the queries concurrently with Promise.all().
// define outside the try/catch so you can rollback if needed
let transaction;
let result;
try {
transaction = await sequelize.transaction({ isolationLevel: 'SERIALIZABLE' });
// run concurrently with Promise.all()
const [ var1, var2 ] = await Promise.all([
// don't await here, but you have to pass the transaction
Model.findByPk({ transaction }),
Model.findOne({ transaction }), // ditto
]);
if (var1.id === 1) {
result = "Whatever result";
} else {
throw new Error("Something wicked");
}
// if you need to commit anything...
// await transaction.commit();
} catch(err) {
/* if you need to roll back anything...
if (transaction) {
await transaction.rollback();
}
*/
console.log(err);
}
return result;
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);
}
I'm new with Promises and in two days a already saw at least five ways to do.
Some ways are older versions, others focus on other languages, so I decide to ask what should be used today.
I did it this way, with 'err' and 'doc' parameters:
Proposals.findById({ ...body, user }, (err, doc) =>
Advertisements.findOneAndUpdate(
{_id: body.advertisement}, {$push: {proposals: doc._id}}
)
)
.then((Proposals) => Proposals.view())
.then(success(res, 201))
.catch(next)
And this:
Proposals.create({ ...body, user })
.then((Proposals) => {
Advertisements.findOneAndUpdate(
{_id: body.advertisement}, {$push: {proposals: Proposals._id}}
)
.catch(Advertisements)
return Proposals.view()
})
.then(success(res, 201))
.catch(next)
Without the "catch" and "return" itsn't work, but I don't know why exactly. :x
The answer to
Using Promises in Mongoose Routes
seems like a beautiful but I couldn't reproduce in code above.
return findUser(userId)
.then((user) => findReceipt(user.bookName))
.then((receipt) => res.status(200).json(receipt))
.catch((err) => res.status(500).json(null))
Are any of these codes more right?
Could you help me to fix the second code?
Thank you.
Let me detail the syntax of the first block of code:
Proposals.findById({ ...body, user }, (err, doc) => {
return Advertisements.findOneAndUpdate(
{_id: body.advertisement}, {$push: {proposals: doc._id}}
);
})
.then((Proposals) => { return Proposals.view() })
.then(success(res, 201))
.catch(next)
In ES6 syntax, this function:
() => ('test');
Equals to
() => { return 'test' };
So I believe your main problem is because of not understanding exactly the ES6 syntax at the beginning.
You should follow these best practices when it comes to Promises:
Avoid nesting catch in Promises
Chain Promises by returning them and use them in the then
Hope it helps.
There are number of ways to handle promises in Mongoose. The exact way that you decide to implement it is left to personal preference, but you need to make sure that your order of execution is as expected. In your first example you are mixing a callback with promises, which I personally think is not optimal. The third example is cleaner as it is a chain of promises being passed down via the then calls.
The important thing to note is that if you want to run async code inside your then calls, you need to ensure you return a Promise that resolves at the right moment. In your second example, you are possibly returning before your findOneAndUpdate call is finished executing. You can avoid this in a number of ways. One option would be to wrap the second call inside a Promise. I give you an idea below by mixing parts of your second and third examples and assuming that the return value from proposal.view() is not async. I use explicit return statements for clarity.
Proposals.create({ ...body, user })
.then((proposal) => {
// call returns a new promise
return new Promise((resolve, reject) => {
// make second async call to database
Advertisements.findOneAndUpdate(
{ _id: body.advertisement },
{ $push: { proposals: proposal._id } },
)
.then(() => {
// promise resolves here and passes return
// value of proposal.view() to next `then`
return resolve(proposal.view())
});
});
})
// consume the value from proposal.view() and send it in response
.then(proposal => res.status(200).json(proposal))
.catch(next)
This would mean that the return value of proposal.view() will only be passed onto the next then once the findOneAndUpdate call is complete.
The example code is not waiting for, or using the result of the update.
In promises returning a promise and using .then waits for that async operation. You can then check the result if required:
Proposals.create({ ...body, user })
.then((proposal) => {
return Advertisements.findOneAndUpdate(
{_id: body.advertisement}, {$push: {proposals: proposal._id}}
)
})
.then((update) => {
if (!update) throw new Error(`Could not find advertisement for "${user}"`)
success(res, 201))
})
.catch(next)
Or the equivalent ES2017 code using await to wait for the promise to resolve, which should help reason how the above Promise .then code flow works.
async function handler(){
try {
let proposal = await Proposals.create({ ...body, user })
let update = await Advertisements.findOneAndUpdate(
{_id: body.advertisement}, {$push: {proposals: proposal._id}}
)
if (!update) throw new Error(`Could not find advertisement for "${user}"`)
success(res, 201)
}
catch (err) {
next(err)
}
}
I'm curerntly playing with Typescript and ionic and also new to async-await on javascript. I'm trying to get a good undestanding of promises but I can't figure out how to wrap methods which call multiple promises to return a promise. I'll try to elaborate better:
Basically, I have an object which can create a SQLite database, and when the create() method is invoked, it would return a Promise with the actual database object once it has been created.
Then, when the promise resolves and the DB object is returned, I need to use it to execute some statements in a transaction to create all the tables, and a new promise is returned when I invoke the execution of all the statements in the transaction.
Then when the transaction is finished and everything went good, I need to assign the database object to a class property and set a flag indicating database is created and ready to go.
So I thought it would be a good idea to wrap this database initialization stuff in a method called createDatabase() or something like that, which returns a Promise<SQLiteObject>, where SQLiteObject represents the database. This method would be called on initialization and should return the SQLiteObject representing the database once everything went OK, or throw an error which I would log in the .catch() method of the Promise.
I have a basic understanding of promises and how to use then() and catch() methods but I'm a bit confused when I have to create the database, then do something else when the promise resolves, and when everything is done, return a Promise containing the DB object, which is an instance of the class SQLiteObject.
CODE
Below is my current Typescript code. It's not valid typescript as I don't know how to return the Promise<SQLiteObject> from the async function.
async createDatabase(): Promise<SQLiteObject> {
this.sqlite.create({
name: this.dbName,
location: this.dbLocation
}).then( (db: SQLiteObject) => {
// Insert all tables.
let createTableParam: string = `CREATE TABLE IF NOT EXISTS param (name PRIMARY KEY NOT NULL, value TEXT)`;
let createTableNews: string = `CREATE TABLE IF NOT EXISTS news (id PRIMARY KEY NOT NULL,title TEXT,
content TEXT, image TEXT, date DATE)`;
db.transaction(tx => {
tx.executeSql(createTableParam);
tx.executeSql(createTableNews);
// Add here more tables to create if needed
}
)
.then( () => {
console.log('Tables were created');
this.isActive = true;
})
.catch(error => {
console.log(`Error creating tables - ${error}`);
});
}).catch(
error => console.log(`Error at SQLite initialization - ${error}`)
);
}
RESEARCH SO FAR
I have read this question here which seems related but I am
confused on how to implement on my code
Checked async/await on MDN
This question here also seems related but still don't really understand how to do it.
Typescript promises at "Typescript Deep Dive"
Async/Await at "Typescript Deep Dive"
You used async, so that means you can use await inside the function any time you have a Promise and write the code almost as though it were synchronous.
async createDatabase(): Promise<SQLiteObject> {
let db: SQLiteObject;
try {
db = await this.sqlite.create({
name: this.dbName,
location: this.dbLocation
});
} catch(error) {
console.log(`Error at SQLite initialization - ${error}`);
return;
);
// Insert all tables.
let createTableParam: string = `CREATE TABLE IF NOT EXISTS param (name PRIMARY KEY NOT NULL, value TEXT)`;
let createTableNews: string = `CREATE TABLE IF NOT EXISTS news (id PRIMARY KEY NOT NULL,title TEXT,
content TEXT, image TEXT, date DATE)`;
try {
await db.transaction(tx => {
tx.executeSql(createTableParam);
tx.executeSql(createTableNews);
// Add here more tables to create if needed
}
);
console.log('Tables were created');
this.isActive = true;
return db;
catch(error) {
console.log(`Error creating tables - ${error}`);
});
}
Without the await you need to be sure to return that initial promise.
return this.sqlite.create({...
and then again further down you can return the db object:
this.isActive = true;
return db;
Also you should avoid nesting the .then() handlers: when you get another promise just return it from the first handler and chain another .then on the end:
createDatabase(): Promise<SQLiteObject> {
let database: SQLiteObject = null;
return this.sqlite.create({
name: this.dbName,
location: this.dbLocation
})
.catch(error => console.log(`Error at SQLite initialization - ${error}`))
.then( (db: SQLiteObject) => {
// Insert all tables.
let createTableParam: string = `CREATE TABLE IF NOT EXISTS param (name PRIMARY KEY NOT NULL, value TEXT)`;
let createTableNews: string = `CREATE TABLE IF NOT EXISTS news (id PRIMARY KEY NOT NULL,title TEXT,
content TEXT, image TEXT, date DATE)`;
database = db;
return db.transaction(tx => {
tx.executeSql(createTableParam);
tx.executeSql(createTableNews);
// Add here more tables to create if needed
}
);
})
.then( () => {
console.log('Tables were created');
this.isActive = true;
return database;
})
.catch(error => console.log(`Error creating tables - ${error}`));
It appears you're not resolving the promise.
A promise must be resolved or rejected so a async function is capable of responding with a value.
From TypeScript Deep Dive:
const promise = new Promise((resolve, reject) => {
resolve(123);
});
promise.then((res) => {
console.log('I get called:', res === 123); // I get called: true
});
promise.catch((err) => {
// This is never called
});
So in my opinion you should create a promise, and when the DB is created and everything resolve it, if there's a problem with the DB creation, reject it.
Remember you can chain promises, so you could chain them as you create your database.
From TypeScript Deep Dive:
The chain-ability of promises is the heart of the benefit that promises provide. Once you have a promise, from that point on, you use the then function to create a chain of promises.
Check this URL for more info about promises
Hope it helps! :)