Can sequelize.transaction() take async function as callback? - javascript

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;

Related

Wait for a query in MongoDB

I have this async method written in Typescript to query, with the nodejs driver, a MongoDB; the compiler indicates that the "await" before "myConnectedClient" has no effect on the type of this expression; I'm confused: is the call to the aggregate() asynchronous? So, I have to wait, or not?Thanks.
async findQuery<T>(
collection: string,
findParams: Query<T>,
sort: Sort<T>,
myConnectedClient: MongoClient
) {
const firstResult = await myConnectedClient // the compiler indicates await is useless
.db("ZZZ_TEST_ALL")
.collection("my_collection_01")
.aggregate<string>([{ $project: { _id: 0, name: 1 } }]);
firstResult.forEach((field) => {
console.log(`Field: ${field}`);
});
}
UPDATE: I have to add .toArray() after the .aggregate() call; but why? Can anybody explain me the mechanism? aggregate() has not callback and does not return a promise? Are there alternatives to .toArray()? Thanks.
// now await it's ok
const firstResult = await myConnectedClient
.db("ZZZ_TEST_ALL")
.collection("my_collection_01")
.aggregate<string>([{ $project: { _id: 0, name: 1 } }]).toArray();
Aggregate is sync and returns an AggregationCursor.
The cursor has number of async methods to retrieve actual data: toArray, forEach, or simple iterator
In the first snippet firstResult is the cursor, so there is no need to await.
You use firstResult.forEach to log the results. It does return the promise but you ignore it, which will bite you - the promise returned by findQuery will be resolved immediately while forEach will iterate results in parallel. To keep the promise chain you should do
const firstResult = myConnectedClient.......;
return firstResult.forEach(......);
so the promise returned from the findQuery will be resolved only when forEach is resolved, e.g. you finish iterate the results.
In the second "update" snippet, the firstResult is the data, so you need the await to get it from toArray(). The equivalent with explicit cursor would be:
const cursor = myConnectedClient.......;
const firstResult = await cursor.toArray();

Firestore Return True or False instead of the Promise Object

So I have this function to store data in Firestore database but I wanted to check if the value exists already and based on that I want to return a boolean.
I kept trying to solve this with async await but it did not seem to work. Finally when I added a return before performanceRef.get(), it solved the issue. Even though it solved the issue I'm not clear why. I know it must have something to do with async. Can someone please explain why adding that return solved the issue?
export const createUserPerformanceDocument = async (user, currentDate, answer ) => {
const createdAt = currentDate.toLocaleDateString('en-US');
const performanceRef = firestore.doc(`performance/${user}`);
return performanceRef.get()
.then(doc => {
if(doc.exists) {
const docData = doc.data();
if (docData[createdAt]) {
return false
} else {
try {
performanceRef.set({
[createdAt]: {
Question: answer
}
}, { merge: true })
return true
} catch(error) {
console.log('Error creating performance data!', error.message);
return false
}
}
} else {
console.log("This user does not exist in the database");
}
})
}
It is not return what solved issue, probably there were some issues in your async/await code, and when you rewrote it into then callback it worked as expected.
Feel free to post your async/await version if you want more clear answer.
P.S. You don't use await keyword, so you don't need async modifier before function
Update from comments
Await keyword simply helps us stop function execution on current line and wait for promise to resolve. If you want to get result of the promise(it is named doc in your then callback) you need to store it in some constant:
const doc = await performanceRef.get()
So there you have it and can perform any kinds of validation like you do in then callback.
If you want to return validation result from this function, simply use return keyword like you already did.

How do I return a value in a promise? (Firebase.get())

I've been struggling for the past four hours on this. I am writing a function to see if a property named "data" exists in my Firebase storage. If it does, I want to do one thing, if it doesn't I want to do something else. However, I cannot figure out how this asynchronous stuff works for the life of me. I simplified my code below. Basically I just want to wait for the data to be fetched before I reach the if/else. I've been playing around with different options but keep getting errors or some other issue. The code below is the closest I've gotten to working where the code doesn't crash but even if "data" does not exist in the Firestore, I'm always going through the else clause and I don't know why. Can someone help me figure out what I am doing wrong?
const fetchDataFromDB = async (user) => {
let query = db
.collection("users")
.doc(user)
.get()
.then((doc) => {
doc.data();
console.log(doc.data().data);
});
return await query;
};
export const getSchedule = (miles, user) => {
const firebaseData = fetchDataFromDB(user);
console.log(firebaseData);
// WAIT FOR FETCH BEFORE CONTINUING
if (!firebaseData) {
console.log("NOT getting data from FB");
return;
} else {
console.log("getting data from FB");
return;
}
};
Change up the code as follows:
const fetchDataFromDB = (user) => {
return db
.collection("users")
.doc(user)
.get()
.then((doc) => {
const data = doc.data();
console.log(data);
return data;
});
};
export const getSchedule = async (miles, user) => {
const firebaseData = await fetchDataFromDB(user);
console.log(firebaseData);
if (!firebaseData) {
console.log("NOT getting data from FB");
return;
} else {
console.log("getting data from FB");
return;
}
};
The point to remember about async await is that it doesn't really make asynchronous calls synchronous, it just makes them look that way so that your code is a bit less unwieldy with wrapped promises and the like. Every async function returns a promise, so if you want to deal with what it returns, you need to either deal with the promise directly (using .then...), or by using another await. In the latter case, you of course need to declare the consuming function as async as well.
With regards to the first function, there's no need for the async await there. Just return the promise. (Thanks #charlieftl for pointing out the problem in that code)

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.

What are the down sides to wrapping promises in an object that resolves them?

I'm working on a new framework of microservices built in Node 8 and trying to simplify some of the logic required for passing Promises around between services.
I have a function I import in each service called StandardPromise which you can pass a Promise to. StandardPromise will call .then() on the promise and place the result in an object. If the promise was resolved it will be placed in the data attribute, if was rejected or threw an error then that will go in the err attribute.
The result of the above is that when a service receives a standardized promise by awaiting a call to another service, it can just check if there's anything in err and move forward with data if err is empty. This flow is significantly simpler than having .then() and .catch() blocks in every function.
I'm pretty happy with how it's turning out, and it seems to be working great, but since I haven't seen many examples of this kind of flow I want to know if there's something I'm missing that makes this a terrible idea or an antipattern or anything like that.
Here's a simplified, somewhat pseudocode example:
Service1:
const sp = require('./standardPromise');
const rp = require('request-promise-native');
function ex() {
// Wrap the Promise returned from rp as a "standardPromise"
return sp(rp.get({url: 'https://example.com'}));
}
Service2:
const Service1 = require('./Service1');
async function ex2() {
var res = await Service1.ex();
if (res.err) {
// Do error stuff
console.error(res.err);
return;
}
// Here we know res.data is our clean data
// Do whatever with res.data
return res.data;
}
standardPromise:
module.exports = function(promise) {
try {
return promise.then((data) => {
return {err: undefined, data: data};
}).catch((err) => {
return Promise.resolve({err: err, data: undefined});
});
} catch(err) {
console.error('promise_resolution_error', err);
return Promise.resolve({err: err, data: undefined});
}
}
It can just check if there's anything in err and move forward with data if err is empty. This flow is significantly simpler than having .then() and .catch() blocks in every function.
No, this is much more complicated, as you always have to check for your err. The point of promises is to not have .catch() blocks in every function, as most functions do not deal with errors. This is a significant advantage over the old nodeback pattern.
You would drop your standardPromise stuff and just write
// Service1:
const rp = require('request-promise-native');
function ex() {
return rp.get({url: 'https://example.com'});
}
// Service2:
const Service1 = require('./Service1');
async function ex2() {
try {
var data = await Service1.ex();
} catch(err) {
// Do error stuff
console.error(err);
return;
}
// Here we know data is our clean data
// Do whatever with data
return data;
}
or actually simpler with then for handling errors:
// Service2:
const Service1 = require('./Service1');
function ex2() {
return Service1.ex().then(data => {
// Here we know data is our clean data
// Do whatever with data
return data;
}, err => {
// Do error stuff
console.error(err);
});
}

Categories

Resources