Reputation: 375
I am currently building a Nodejs, Express, Sequelize (w. PostgreSQL) app, and have run into a few problems with using promises together with transactions and loops.
I am trying to figure out how to use a for loops in a transaction. I am trying to loop through a list of members and create a new user in the database for each of them.
I know the following code is wrong but it shows what I am trying to do.
Can anyone point me in the right direction?
var members = req.body.members;
models.sequelize.transaction(function (t) {
for (var i = 0; i < members.length; i++) {
return models.User.create({'firstname':members[i], 'email':members[i], 'pending':true}, {transaction: t}).then(function(user) {
return user.addInvitations([group], {transaction: t}).then(function(){}).catch(function(err){return next(err);});
})
};
}).then(function (result) {
console.log("YAY");
}).catch(function (err) {
console.log("NO!!!");
return next(err);
});
Upvotes: 16
Views: 25253
Reputation: 41
if someone is looking for a solution with typescript v4.0.5 using async and await here is what worked out for me. Maybe you can use it on your javascript aplication too but it will depend on the version of it.
const array = ['one','two','three'];
const createdTransaction = sequelize.transaction();
const promises = array.map(async item => {
await model.create({
name: item,
},
{ transaction: createdTransaction },
);
});
Promise.all(promises).then(async values => {
await createdTransaction.commit();
});
Upvotes: 0
Reputation: 276306
You'll need to use the built in looping constructs of bluebird which ships with sequelize:
var members = req.body.members;
models.sequelize.transaction(t =>
Promise.map(members, m => // create all users
models.User.create({firstname: m, email: m, 'pending':true}, {transaction: t})
).map(user => // then for each user add the invitation
user.addInvitations([group], {transaction: t}) // add invitations
)).nodeify(next); // convert to node err-back syntax for express
Upvotes: 7
Reputation: 994
Depending on your implementation of Node.js this may help. I have the same setup using express, POSTGRES and sequelize.
Personally I'd prefer the async/await (ES6) implementation over then/catch as it is easier to read. Also creating a function that can be called externally improves re-usability.
async function createMemeber(req) {
let members = req.body.members;
for (var i = 0; i < members.length; i++) {
// Must be defined inside loop but outside the try to reset for each new member;
let transaction = models.sequelize.transaction();
try {
// Start transaction block.
let user = await models.User.create({'firstname':members[i], 'email':members[i], 'pending':true}, {transaction});
await user.addInvitations([group], {transaction}));
// if successful commit the record. Else in the catch block rollback the record.
transaction.commit();
// End transaction block.
return user;
} catch (error) {
console.log("An unexpected error occurred creating user record: ", error);
transaction.rollback();
// Throw the error back to the caller and handle it there. i.e. the called express route.
throw error;
}
}
}
Upvotes: 2
Reputation: 1
First: https://caolan.github.io/async/docs.html
So, easily:
// requiring...
const async = require('async');
// exports...
createAllAsync: (array, transaction) => {
return new Promise((resolve, reject) => {
var results = [];
async.forEachOf(array, (elem, index, callback) => {
results.push(models.Model.create(elem, {transaction}));
callback();
}, err => {
if (err) {
reject(err);
}
else {
resolve(results);
}
});
});
}
Upvotes: -1
Reputation: 14824
You should use a Promise.all
var members = req.body.members;
models.sequelize.transaction(function (t) {
var promises = []
for (var i = 0; i < members.length; i++) {
var newPromise = models.User.create({'firstname':members[i], 'email':members[i], 'pending':true}, {transaction: t});
promises.push(newPromise);
};
return Promise.all(promises).then(function(users) {
var userPromises = [];
for (var i = 0; i < users.length; i++) {
userPromises.push(users[i].addInvitations([group], {transaction: t});
}
return Promise.all(userPromises);
});
}).then(function (result) {
console.log("YAY");
}).catch(function (err) {
console.log("NO!!!");
return next(err);
});
I don't believe you need to catch
within sequelize transactions as I think it jumps out to the catch on the transaction
Sorry for formatting. On mobile.
Promise.all will wait for all promises to return (or fail) before running the .then, and the .then
callback will be all the promise data from each array
Upvotes: 37