Упаковка вызовов MongoDB в обещание

Я использую Meteor (1.0.3) в целом, но в одном конкретном случае я использую необработанный серверный маршрут для рендеринга файла - так что я вне метода Meteor.

Я использую команды fs.writeFile/fs.readFile и exec для вызова утилит командной строки Linux.

Единственное, что я хочу сказать, это то, что вызовы узлов, конечно, асинхронны. И поэтому я решил использовать библиотеку узла Q для управления асинхронными обратными вызовами.

Все это работало, пока я не добавил строку для вызова базы данных MongoDB.

Звонок вот так:

var record_name = Mongo_Collection_Name.findOne({_personId: userId}, {fields: {'_id': 0}});

Выдает следующую ошибку:

[Ошибка: не могу ждать без волокна]

Ошибка возникает только тогда, когда я обертываю функцию в Promise.

Например, что-то вроде этого выкинет:

getRecordExample = function () {
  var deferred = Q.defer();
  var record_name = Mongo_Collection_Name.findOne({_personId: userId}, {fields: {'_id': 0}});

  // do something

  // if no error
  deferred.resolve(record_name);

  return deferred.promise;
}

Если я использую библиотеку Meteor Fibers, я не получаю ошибку:

getRecordExample = function () {
  var deferred = Q.defer();
  Fiber = Npm.require('fibers');
  var record_name

  Fiber(function () {
    record_name = Mongo_Collection_Name.findOne({_personId: userId});
  }).run()
  // do something

  // if no error
  deferred.resolve(record_name);

  return deferred.promise;
}

но переменная record_name не определена за пределами волокна, поэтому, насколько я могу судить, у меня нет способа передать переменную за пределы области действия Fiber.

Более точный пример

Это немного долго, поэтому вы должны прокрутить вниз, чтобы увидеть все это. Я в основном строю рабочий процесс здесь, чтобы были процессы и подпроцессы.

// both/routes.js
Router.route('/get-route', function(req, res) {
  // get the userId then start the workflow below

  // using Promises here because these were firing concurrently
  Q(userId)
  .then(process_1)
  .then(process_2)
  .done();
}, { name: 'server-side-ir-route', where: 'server' }

// server.js
process_1 = function (userId) {
  sub_process_1(userId);

  sub_process_2(userId);

  return userId;
}

process_2 = function (userId) {
  sub_process_3(userId);

  sub_process_4(userId);

  return userId;
}

sub_process_1 = function (userId) {
  var result = get_record_1(userId);

  // do stuff with result

  // using Q library to call out to async fs.writeFile, return Promise
  fs_writeFile_promise(result)
  .catch(function (error) {
    console.log('error in sub_process_1_write', error);
  })
  .done(function () {
    console.log('done with sub_process_1');
  }

  return userId;
}.future() // <-- if no future() here, the exception is thrown.

sub_process_2 = function (userId) {
  var result = get_record_2(userId);

  // do stuff with result

  // using Q library to call out to async fs.writeFile, return Promise
  fs_writeFile_promise(result)
  .catch(function (error) {
    console.log('error in sub_process_1_write', error);
  })
  .done(function () {
    console.log('done with sub_process_1');
  }

  return userId;
}.future()

// async because of I/O operation (I think)
get_record_1 = function (userId) {
  var record_1 = Mongo_Collection_Name.findOne({'userId': userId});
  // do stuff
  return record_1;
}
get_record_2 = function (userId) {
  var record_2 = Mongo_Collection_Name.findOne({'userId': userId});
  // do stuff
  return record_2;
}

// async operation using Q library to return a Promise
fs_writeFile_promise = function (obj) {
  var deferred = Q.defer();
  fs.writeFile(obj.file, obj.datas, function (err, result) {
    if (err) deferred.reject(err);
    else deferred.resolve('write data completed');
  });
  return deferred.promise;
}

А пока давайте предположим, что функция process_2 точно такая же, как process_1

Кроме того, мы должны предположить, что у меня есть console.log('step_start') и console.log('step_end') в каждой функции. Вот как это будет выглядеть в командной строке:

  • запускать процессы
  • конечные процессы
  • запуск процессов 1
  • конечные процессы 1
  • запустить процессы 2
  • начать подпроцессы 1
  • получать запись 1
  • запустить подпроцессы 2
  • получить запись 2
  • возвращение записи 1
  • конец подпроцессов 1
  • называется writeData в подпроцессе 1
  • возвращение записи 2
  • называется writeData в подпроцессе 2
  • конечные процессы 2
  • окончание подпроцессов 1

Причина, по которой мне пришлось поместить Fiber (future) в функцию sub_process_1(), заключалась в том, что когда я поместил функцию process_1 () в цепочку Q вверху, я получил сообщение об ошибке: "Не могу ждать без волокна".

Если я удаляю process_1 () в цепочке Q сверху и удаляю.future() из sub_process_1(), исключение не выдается.

Вопросы

  • Почему вызов коллекции Mongo в Promise вызывает ошибку оптоволокна в приложении Meteor?
  • Вызывает ли вызов асинхронной функции в функции синхронизации вообще функцию синхронизации, чтобы стать асинхронной функцией?
  • Как мне решить эту проблему?

1 ответ

Решение

Наиболее распространенный способ решить эту проблему - обернуть ваши асинхронные обратные вызовы, которые используют функции Метеор в Meteor.bindEnvironment(),

Если вы используете базовый пакет Meteor WebApp для обработки маршрута на стороне сервера, код будет выглядеть следующим образом (также в meteorpad):

WebApp.connectHandlers.use(
  '/test',
  Meteor.bindEnvironment(function(req, res, next) {
    var someSyncData = Players.findOne();
    res.write(JSON.stringify(someSyncData));
    res.end();
  })
);

Сама работа с волокнами или обещаниями не нужна, если вы не пытаетесь одновременно запустить несколько асинхронных событий.

Для работы с чтением файлов или другими функциями, которые еще не синхронизированы, Meteor также предоставляет Meteor.wrapAsync() сделать их синхронными.

Есть также пакеты и страница справки, которые дают вам другие альтернативы высокого уровня.

Другие вопросы по тегам