Search code examples
node.jspromisebluebirdsequelize.js

Create/update with Sequelize on an array of items


I created a function to:

  • take an array of 'labels' and look for whether they have a record in the db already
  • create those which don't exist,
  • and update those which do exist
  • return a json array reporting on each item, whether they were updated/created, or resulted in an error

I managed to make it work but I feel like I just made some ugly dogs' dinner!

var models = require("../models");
var Promise = models.Sequelize.Promise;

module.exports = {

    addBeans: function (req, callback) {

        Promise.map(req.body.beansArr, function (bean) {
            return models.Portfolio.findOrCreate({where: {label: bean}}, {label: bean});

        }).then(function (results) {   // Array of 'instance' and 'created' for each bean "findOrCreate(where, [defaults], [options]) -> Promise<Instance>"
            var promisesArr = [];
            results.forEach(function (result) {
                if (result[1]) {   // result[1] = wasCreated
                    promisesArr.push(Promise.resolve([result[0].dataValues.label, "created"]));   
                } else {
                    promisesArr.push(
                        models.Portfolio.update({label: result[0].dataValues.label},
                            {where: {label: result[0].dataValues.label}}).then(function () {
                                return Promise.resolve([result[0].dataValues.label, "updated"])
                            })
                    );
                }
            }); 
            return promisesArr;

        // When it's all done create a JSON response
        }).then(function (results) {
            var resultObj = {items: []};  // JSON to return at the end
            Promise.settle(results).then(function (promiseinstances) {

                for (var i = 0; i < promiseInstances.length; i++) {
                    if (promiseInstances[i].isFulfilled()) {
                        resultObj.items.push({
                            item: {
                                label: promiseInstances[i].value()[0],
                                result: promiseInstances[i].value()[1],
                                error: ''
                            }
                        });
                    }
                    else if (promiseInstances[i].isRejected()){
                        resultObj.items.push({
                            label: promiseInstances[i].value()[0],
                            result: 'error',
                            error: promiseInstances[i].reason()
                        });
                    }
                }

            // Send the response back to caller
            }).then(function () {
                return callback(null, resultObj);
            }, function (e) {
                return callback(e, resultObj);
            });

        });
    }

};

Question:

  • Is there an easier or more obvious way to create/update values with Sequelize?
  • Is my use of Promise.settle() appropriate for this case? I have the feeling I made this more complicated than it needs to be.

I am new to Sequelize and using Promises, I'd appreciate if someone could advise on this.


Solution

  • I feel like this would work better on CodeReview.SE but I can see a few issues.

    Is there an easier or more obvious way to create/update values with Sequelize?

    Well, for one thing:

    .then(function(array){
       var newArr = [];
       array.forEach(function(elem){
           newArr.push(fn(elem);
       }
       return newArr;
    });
    

    Is just

    .map(fn)
    

    Additionally, promises assimilate so you can return val; from a .then you don't have to return Promise.resolve(val);.

    So:

    ).then(function (results) {   // Array of 'instance' and 'created' for each bean "findOrCreate(where, [defaults], [options]) -> Promise<Instance>"
        var promisesArr = [];
        results.forEach(function (result) {
            if (result[1]) {   // result[1] = wasCreated
                promisesArr.push(Promise.resolve([result[0].dataValues.label, "created"]));   
            } else {
                promisesArr.push(
                    models.Portfolio.update({label: result[0].dataValues.label},
                        {where: {label: result[0].dataValues.label}}).then(function () {
                            return Promise.resolve([result[0].dataValues.label, "updated"])
                        })
                );
            }
        }); 
        return promisesArr;
    })
    

    Is just

    .map(function(result){
         if(result[1]) return [result[0].dataValues.label, "created"];
         return  models.Portfolio.update({label: result[0].dataValues.label},
                        {where: {label: result[0].dataValues.label}}).
                        return([result[0].dataValues.label, "updated"]);
     });
    

    However, since you want it to work regardless of it being resolved, you'd have to do:

    .then(function(results){
         return results.map(function(result){
         if(result[1]) return [result[0].dataValues.label, "created"];
         return  models.Portfolio.update({label: result[0].dataValues.label},
                        {where: {label: result[0].dataValues.label}}).
                        return([result[0].dataValues.label, "updated"]);
     });
     });
    

    Which means it'll resolve regardless, then you'd call .settle():

    .settle().then(function(results){
         // your settle logic here
    });
    

    Note that the last:

    }).then(function () {
        return callback(null, resultObj);
    }, function (e) {
        return callback(e, resultObj);
    });
    

    Is simply:

    .nodeify(callback);
    

    However, I recommend sticking to promises.