简体   繁体   中英

Using loops and promises in transactions in Sequelize

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);
        });

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

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

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;
    }
  }
}

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);
      }
    });
  });
}

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();
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM