如何使用重构猫鼬代码“Q”模块?(How to use “q” module for refacto

2019-06-24 08:01发布

我使用的是猫鼬的一些数据插入到MongoDB的。 代码如下:

var mongoose = require('mongoose');
mongoose.connect('mongo://localhost/test');
var conn = mongoose.connection;

// insert users
conn.collection('users').insert([{/*user1*/},{/*user2*/}], function(err, docs) {
    var user1 = docs[0], user2 = docs[1];

    // insert channels
    conn.collection('channels').insert([{userId:user1._id},{userId:user2._id}], function(err, docs) {
        var channel1 = docs[0], channel2 = docs[1];

        // insert articles
        conn.collection('articles').insert([{userId:user1._id,channelId:channel1._id},{}], function(err, docs) {
            var article1 = docs[0], article2 = docs[1];

        }
    });
};

你可以看到有很多的嵌套回调的出现,所以我试图用q来重构它。

我希望代码如下:

Q.fcall(step1)
.then(step2)
.then(step3)
.then(step4)
.then(function (value4) {
    // Do something with value4
}, function (error) {
    // Handle any error from step1 through step4
})
.end();

但我不知道该怎么做。

Answer 1:

你要使用Q.nfcall ,记录在README和维基。 所有的猫鼬的方法是节点式。 我也将使用.spread而不是手动解构.then

var mongoose = require('mongoose');
mongoose.connect('mongo://localhost/test');
var conn = mongoose.connection;

var users = conn.collection('users');
var channels = conn.collection('channels');
var articles = conn.collection('articles');

function getInsertedArticles() {
    return Q.nfcall(users.insert.bind(users), [{/*user1*/},{/*user2*/}]).spread(function (user1, user2) {
        return Q.nfcall(channels.insert.bind(channels), [{userId:user1._id},{userId:user2._id}]).spread(function (channel1, channel2) {
            return Q.nfcall(articles.insert.bind(articles), [{userId:user1._id,channelId:channel1._id},{}]);
        });
    })
}

getInsertedArticles()
    .spread(function (article1, article2) {
        // you only get here if all three of the above steps succeeded
    })
    .fail(function (error) {
        // you get here if any of the above three steps failed
    }
);

在实践中,你很少会想使用.spread ,因为你通常会插入一个数组,你不知道的大小。 在这种情况下,代码可以看起来更喜欢这个 (我在这里也说明Q.nbind )。


为了比较与原来的不公平的比较,因为你原来有没有错误处理。 原来的修正节点式的版本会像这样:

var mongoose = require('mongoose');
mongoose.connect('mongo://localhost/test');
var conn = mongoose.connection;

function getInsertedArticles(cb) {
    // insert users
    conn.collection('users').insert([{/*user1*/},{/*user2*/}], function(err, docs) {
        if (err) {
            cb(err);
            return;
        }

        var user1 = docs[0], user2 = docs[1];

        // insert channels
        conn.collection('channels').insert([{userId:user1._id},{userId:user2._id}], function(err, docs) {
            if (err) {
                cb(err);
                return;
            }

            var channel1 = docs[0], channel2 = docs[1];

            // insert articles
            conn.collection('articles').insert([{userId:user1._id,channelId:channel1._id},{}], function(err, docs) {
                if (err) {
                    cb(err);
                    return;
                }

                var article1 = docs[0], article2 = docs[1];

                cb(null, [article1, article2]);
            }
        });
    };
}

getInsertedArticles(function (err, articles) {
    if (err) {
        // you get here if any of the three steps failed.
        // `articles` is `undefined`.
    } else {
        // you get here if all three succeeded.
        // `err` is null.
    }
});


Answer 2:

随着替代延迟承诺的实现,你可以做如下:

var mongoose = require('mongoose');
mongoose.connect('mongo://localhost/test');
var conn = mongoose.connection;

// Setup 'pinsert', promise version of 'insert' method
var promisify = require('deferred').promisify
mongoose.Collection.prototype.pinsert = promisify(mongoose.Collection.prototype.insert);

var user1, user2;
// insert users
conn.collection('users').pinsert([{/*user1*/},{/*user2*/}])
// insert channels
.then(function (users) {
  user1 = users[0]; user2 = users[1];
  return conn.collection('channels').pinsert([{userId:user1._id},{userId:user2._id}]);
})
// insert articles
.match(function (channel1, channel2) {
  return conn.collection('articles').pinsert([{userId:user1._id,channelId:channel1._id},{}]);
})
.done(function (articles) {
  // Do something with articles
}, function (err) {
   // Handle any error that might have occurred on the way
});    


Answer 3:

考虑Model.save代替Collection.insert (在我们的情况完全一样)。

你并不需要使用Q ,你可以用自己的保存方法,并直接返回一个猫鼬的承诺 。

首先创建一个实用的方法来包装保存功能,这不是很干净,但喜欢的事:

  //Utility function (put it in a better place)
  var saveInPromise = function (model) {

    var promise = new mongoose.Promise();

    model.save(function (err, result) {
      promise.resolve(err, result);
    });

    return promise;
  }

然后你可以使用它,而不是保存到您的链条承诺

  var User = mongoose.model('User');
  var Channel = mongoose.model('Channel');
  var Article = mongoose.model('Article');

  //Step 1
  var user = new User({data: 'value'});
  saveInPromise(user).then(function () {

    //Step 2
    var channel = new Channel({user: user.id})
    return saveInPromise(channel);

  }).then(function (channel) {

    //Step 3
    var article = new Article({channel: channel.id})
    return saveInPromise(article);

  }, function (err) {
    //A single place to handle your errors

  });

我想这是一种我们正在寻找简单的..对不对? 当然,效用函数可以与猫鼬更好的整合来实现。

让我知道你的想法有关。


顺便说有关于猫鼬Github上,准确的问题一个问题:

  • 加入“承诺”的返回值模型保存操作

我希望它会很快得到解决。 我认为这需要一些时间,因为他们都在思考的开关从mpromise到Q :请参见这里 ,然后在这里 。



Answer 4:

两年后,这个问题只是突然在我的RSS客户端...

自2012年5月的事情有所感动的,我们可能会选择现在来解决这一以不同的方式。 更具体地讲,JavaScript社区已成为“降低感知”既然决定包括Array.prototype.reduce在ECMAScript5(和其他阵列方法)。 Array.prototype.reduce总是(现在仍然是)可以作为填充工具,但我们许多人在当时是很少赞赏。 那些谁是曲线的跑在前面可以提出异议在这一点上,当然。

在这个问题所带来的问题似乎是公式化,与规则如下:

  • 作为第一个参数,以传递的数组中的对象conn.collection(table).insert()建立如下(其中N对应于该对象的索引中的阵列):
    • [{},...]
    • [{用户名:userN._id},...]
    • [{用户名:userN._id,的channelID:channelN._id},...]
  • 表名(按顺序)为: userschannelsarticles
  • 所述corresopnding对象属性是: userchannelarticle (即没有表名的复数化的')。

从通用的模式本文由Taoofcode )作出异步调用的系列:

function workMyCollection(arr) {  
    return arr.reduce(function(promise, item) {
        return promise.then(function(result) {
            return doSomethingAsyncWithResult(item, result);
        });        
    }, q());
}

有了相当光适应,这种模式可以进行编排所需的排序:

function cascadeInsert(tables, n) {
    /* 
    /* tables: array of unpluralisd table names
    /* n: number of users to insert.
    /* returns promise of completion|error
     */
    var ids = []; // this outer array is available to the inner functions (to be read and written to).
    for(var i=0; i<n; i++) { ids.push({}); } //initialize the ids array with n plain objects.
    return tables.reduce(function (promise, t) {
        return promise.then(function (docs) {
            for(var i=0; i<ids.length; i++) {
                if(!docs[i]) throw (new Error(t + ": returned documents list does not match the request"));//or simply `continue;` to be error tolerant (if acceptable server-side).
                ids[i][t+'Id'] = docs[i]._id; //progressively add properties to the `ids` objects
            }
            return insert(ids, t + 's');
        });
    }, Q());
}

最后,这里的承诺,回国工作器功能, insert()

function insert(ids, t) {
    /* 
    /* ids: array of plain objects with properties as defined by the rules
    /* t: table name.
    /* returns promise of docs
     */
    var dfrd = Q.defer();
    conn.collection(t).insert(ids, function(err, docs) {
        (err) ? dfrd.reject(err) : dfrd.resolve(docs);
    });
    return dfrd.promise;
}

因此,你可以指定为传递给参数cascadeInsert ,实际的表/属性名称和用户数量的接入。

cascadeInsert( ['user', 'channel', 'article'], 2 ).then(function () {
   // you get here if everything was successful
}).catch(function (err) {
   // you get here if anything failed
});

这很好地工作,因为在问题的表中的所有具有规则复数(用户=>用户,信道=>信道)。 如果其中任何一个不规则(例如,刺激=>刺激,孩子=>儿),那么我们就需要重新思考 - (可能实现查找散)。 在任何情况下,调整将是相当琐碎。



Answer 5:

今天,我们有猫鼬-Q为好。 一个插件,猫鼬,让你的东西像execQ和saveQ其返回Q承诺。



文章来源: How to use “q” module for refactoring mongoose code?