Igor Kasuan
Igor Kasuan

Reputation: 842

How to associate objects in Sequelize (Node js)?

I have User and File models in Sequlize. User can have several files. I have association db.User.hasMany(db.File, {as: 'Files', foreignKey: 'userId', constraints: false});

I want to init User object with several Files and save it to database.

I wrote next code:

 var files = [];

        var file1 = models.File.build();
        file1.name = "JPEG";

        file1.save().then(function () {

        });

        files.push(file1);

        var file2 = models.File.build();
        file2.name = "PNG";

        file2.save().then(function () {

        });

        files.push(file2);


 var newUser = models.User.build();
       newUser.email = email;
 newUser.save().then(function (usr) {

 files.forEach(function (item) {
     newUser.addFile(item);
});

});

But I found a bug, sometimes several files were not associated with the user.

I found(in nodejs logs) commands(update) for setting foreign keys for these files. But commands were not executed. Foreign keys(userId) of several files were empty.

I think the problem is in asynchronous queries. How to organize code structure to avoid this bug?

Upvotes: 1

Views: 1777

Answers (1)

user3254198
user3254198

Reputation: 759

The problem is exactly what you think it is, the async code.

You need to move the functions inside of the callbacks otherwise the code gets run before the file is created.

JavaScript doesn't wait before moving on to the next line so it'll run the next line whether its done or not. It has no sense of waiting before moving.

So you're basically adding something that doesn't yet exist because it doesn't wait for the file to be saved before moving on.

This would work though, just moving the code inside of the then callbacks:

var files = [];

var file1 = models.File.build();
file1.name = "JPEG";

file1.save().then(function () {
    files.push(file1);

    var file2 = models.File.build();
    file2.name = "PNG";

    file2.save().then(function () {
        files.push(file2);

        var newUser = models.User.build();
        newUser.email = email;
        newUser.save().then(function (usr) {
            files.forEach(function (item) {
                newUser.addFile(item);
            });
        });
    });
});

But that's messy. Instead you can chain promises like this:

var files = [];

var file1 = models.File.build();
file1.name = "JPEG";

file1.save()
  .then(function(file1) {
      files.push(file1);

      var file2 = models.File.build();
      file2.name = "PNG";

      return file2.save();
  })
  .then(function(file2) {
      files.push(file2);

      var newUser = models.User.build();
      newUser.email = email;

      return newUser.save();
  })
  .then(function(newUser) {
       files.forEach(function(item) {
           newUser.addFile(item);
       });
  });

Now that's a bit cleaner but still a bit messy and also a bit confusing. So you can use generator functions instead like this:

var co = require('co');

co(function*() {
    var files = [];

    var file1 = models.File.build();
    file1.name = "JPEG";

    yield file1.save();
    files.push(file1);

    var file2 = models.File.build();
    file2.name = "PNG";

    yield file2.save();
    files.push(file2);

    var newUser = models.User.build();
    newUser.email = email;
    newUser.save();

    files.forEach(function(item) {
        newUser.addFile(item);
    });
});

Now that's much better.

Look closely and you see what's happening. co accepts a generator function which is basically a regular function with asterisks *. This is a special functions which adds yield expression support.

yield expressions basically wait for the then() callback to be called before moving on and if the then callback has a argument then it'll return it as well.

So you can do something like:

var gif = yield models.File.create({
  name: 'gif'
});

instead of:

models.File.create({
  name: 'gif'
}).then(function(gif) {

});

You have to use a small node module called co for this though just npm install --save co

Upvotes: 2

Related Questions