2012-05-11 4 views
17

Я использую мангуст, чтобы вставить некоторые данные в mongodb. Код выглядит следующим образом:Как использовать модуль «q» для рефакторинга кода мангуста?

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

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

ответ

12

Вы должны использовать Q.nfcall, задокументированный in the README и Wiki. Все методы Mongoose являются Node-style. Я также буду использовать .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, так как вы обычно вставляют массив, который вы не знаете размер. В этом случае код может выглядеть более like this (здесь я также иллюстрирую 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. 
    } 
}); 
+4

Спасибо, но ... Я не нахожу его более простым, чем оригинал :( – Freewind

+0

У вашего оригинала вообще нет обработки ошибок. – Domenic

+0

Спасибо за демонстрацию ошибок: – Freewind

4

С альтернативной реализации deferred обещание, вы можете сделать это следующим образом:

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

'conn.collection (' . статьи) pinsert ([{идентификатор пользователя: user1._id': не могу получить 'user1' здесь – Freewind

+0

Freewind, это правда, я упустил это. Я обновил свой пример. В принципе, независимо от того, что вы делаете, вам необходимо внедрить последующие вызовы, чтобы иметь возможность видеть все предыдущие результаты в пределах области действия или присваивать результаты переменным из внешней области. –

+0

Большое спасибо. На самом деле, мне нравится ваше решение намного лучше. но ... поскольку вопрос заключается в «использовании модуля q», я не могу принять ваш ответ здесь. – Freewind

3

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

Вам не нужно использовать Q, вы можете обернуть себя метод save и вернуться непосредственно в Mongoose Promise.

Сначала создайте служебный метод, чтобы обернуть функцию сохранения, это не очень чистые, но что-то вроде:

//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 

    }); 

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

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


Кстати есть вопрос о том, что точной задачи в Mongoose Github:

Я надеюсь, что это собирается быть решена в ближайшее время.Я думаю, что это занимает несколько раз, потому что они думают переключиться с mpromise на Q: См. here, а затем here.

+1

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

2

Два года спустя, этот вопрос просто появился в моем RSS клиент ...

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

Проблема поставлена ​​в вопросе, как представляется, шаблонными, с правилами следующим образом:

  • Объектов в массиве передается в качестве первых паров для conn.collection(table).insert() сборки следующим образом (где N соответствует индексу объекта в массив):
    • [{}, ...]
    • [{идентификатор пользователя: userN._id}, ...]
    • [{идентификатор пользователя: userN._id, channelId: channelN._id}, ...]
  • названия таблиц (в порядке): users, channels, articles.
  • соответствующими объектами являются: user, channel, article (т.е. имена таблиц без плюрализации 's').

Общий шаблон из this article by 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 
}); 

Это хорошо работает, потому что таблицы в вопросе все регулярные множественные (пользователи => пользователей, канал => каналы). Если какой-либо из них был нерегулярным (например, stimulus => stimuli, child => children), тогда нам нужно было бы переосмыслить - (и, вероятно, реализовать хеш-поиск). В любом случае адаптация была бы довольно тривиальной.

1

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

 Смежные вопросы

  • Нет связанных вопросов^_^