Вопрос по asynchronous, mongoose, q, javascript, promise – Как использовать модуль «q» для рефакторинга кода мангуста?

17

Я использую mongoose для вставки некоторых данных в 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();

Но я не знаю, как это сделать.

Ваш Ответ

5   ответов
1

Мангуст-д также. Плагин для mongoose, который дает вам такие вещи, как execQ и saveQ, которые возвращают обещания Q.

2

С мая 2012 года ситуация несколько изменилась, и мы можем решить эту проблему по-другому. Более конкретно, сообщество Javascript стало «осведомленным о снижении» так как решение включитьArray.prototype.reduce (и другие методы Array) в ECMAScript5.Array.prototype.reduce был всегда (и остается) доступным как полифилл, но многие из нас тогда мало ценили. Те, кто бежал впереди кривой, могут возразить на этот счет, конечно.

Проблема, поставленная в вопросе, выглядит формальной, с правилами, изложенными ниже:

The objects in the array passed as the first param to conn.collection(table).insert() build as follows (where N corresponds to the object's index in an array): [ {}, ... ] [ {userId:userN._id}, ... ] [ {userId:userN._id, channelId:channelN._id}, ... ] table names (in order) are : users, channels, articles. the corresopnding object properties are : user, channel, article (ie the table names without the pluralizing 's').

Общий образец отэта статья от Taoofcode) для выполнения асинхронного вызова последовательно:

function workMyCollection(arr) {  
    return arr.reduce(function(promise, item) {
        return promise.then(function(result) {
            return doSomethingAsyncWithResult(item, resul,t);
        });        
    }, 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
});

Это хорошо работает, потому что все таблицы в вопросе имеют обычное множественное число (user = & gt; users, channel = & gt; channel). Если какой-либо из них был нерегулярным (например, стимул => стимулы, ребенок => дети), то нам нужно было бы переосмыслить - (и, вероятно, реализовать хеш поиска). В любом случае адаптация будет довольно тривиальной.

4

отсроченный Выполнение обещания, вы можете сделать это следующим образом:

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
});    
Freewind это правда, я упустил это. Я обновил свой пример. По сути, независимо от того, что вы делаете, вы должны инкапсулировать последующие вызовы, чтобы иметь возможность видеть все предыдущие результаты в пределах области, или присваивать результаты переменным из внешней области.
Большое спасибо. На самом деле, мне нравится ваше решение намного лучше. но ... так как вопросuse module qЯ не могу принять ваш ответ здесь. Freewind
conn.collection('articles').pinsert([{userId:user1._id: не могу получитьuser1 Вот Freewind
нет проблем :) Я просто хотел показать, есть и альтернативные решения
3

Model.save вместоCollection.insert (в нашем случае тоже самое)

You don't need to use 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

  });

Я предполагаю, что это та простота, которую мы ищем ... верно? Конечно, функция полезности может быть реализована с лучшей интеграцией с Mongoose.

Дайте мне знать, что вы думаете об этом.

Кстати, есть проблема именно этой проблемы в Mongoose Github:

Add 'promise' return value to model save operation

Надеюсь, это скоро будет решено. Я думаю, что это занимает несколько раз, потому что они думают о переходе отmpromise вQ: УвидетьВот а потомВот.

Я думаю, что хорошим моментом для добавления служебной функции является прототип модели mongoose.Model.prototype.saveInPromise = function () {...};
13

Q.nfcallдокументально подтвержденов README и вики. Все методы Mongoose имеют стиль Node. Я также буду использовать.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).

Сравнивать с оригиналом не совсем справедливо, потому что в вашем оригинале нет обработки ошибок. Исправленная версия оригинала в стиле Node будет выглядеть так:

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.
    }
});
Спасибо, но ... я не нахожу это проще, чем оригинал :( Freewind
Ваш оригинал не имеет никакой обработки ошибок вообще.

Похожие вопросы