Как правильно сделать синхронный запрос MongoDB в Node.js?

Я использую драйвер Node.JS для MongoDB и хочу выполнить синхронный запрос, например:

function getAThing()
{
    var db = new mongo.Db("mydatabase", server, {});

    db.open(function(err, db)
    {
        db.authenticate("myuser", "mypassword", function(err, success)
        {
            if (success)
            {
                db.collection("Things", function(err, collection)
                {
                    collection.findOne({ name : "bob"}, function(err, thing)
                    {                           
                        return thing;
                    });
                });
            }
        });
    });
}

Проблема в том, что db.open является асинхронным вызовом (он не блокируется), поэтому getAThing возвращает «undefined», и я хочу, чтобы он возвращал результаты запроса. Я уверен, что мог бы использовать какой-то блокирующий механизм, но я хотел бы знать, как правильно сделать что-то подобное.


person Mike Pateras    schedule 19.08.2012    source источник


Ответы (3)


Невозможно сделать это синхронным без какого-то ужасного взлома. Правильный способ — getAThing принять функцию обратного вызова в качестве параметра, а затем вызвать эту функцию, как только thing будет доступна.

function getAThing(callback)
{
    var db = new mongo.Db("mydatabase", server, {});

    db.open(function(err, db)
    {
        db.authenticate("myuser", "mypassword", function(err, success)
        {
            if (success)
            {
                db.collection("Things", function(err, collection)
                {
                    collection.findOne({ name : "bob"}, function(err, thing)
                    {       
                        db.close();                    
                        callback(err, thing);
                    });
                });
            }
        });
    });
}

Обновление Node 7.6+

async/await теперь предоставляет способ кодирования в синхронном стиле при использовании асинхронных API, которые возвращают промисы (как это делает собственный драйвер MongoDB).

Используя этот подход, описанный выше метод можно записать так:

async function getAThing() {
    let db = await mongodb.MongoClient.connect('mongodb://server/mydatabase');
    if (await db.authenticate("myuser", "mypassword")) {
        let thing = await db.collection("Things").findOne({ name: "bob" });
        await db.close();
        return thing;
    }
}

Которую вы затем можете вызвать из другой функции async как let thing = await getAThing();.

Однако стоит отметить, что MongoClient предоставляет пул соединений, поэтому вы не должны открывать и закрывать его в этом методе. Вместо этого вызовите MongoClient.connect во время запуска приложения, а затем упростите свой метод до:

async function getAThing() {
    return db.collection("Things").findOne({ name: "bob" });
}

Обратите внимание, что мы не вызываем await внутри метода, вместо этого напрямую возвращаем промис, возвращенный findOne.

person JohnnyHK    schedule 19.08.2012
comment
Спасибо Джонни за этот обходной путь! Я бы хотел, чтобы был простой выход из коробки ... даже писать простую функцию if_exists() неудобно... Кстати, если кто-нибудь знает более простой способ или обновление драйвера, пожалуйста, опубликуйте его здесь. - person Logan; 14.12.2012
comment
Вы всегда можете использовать асинхронную библиотеку, чтобы избежать идентификации doom github.com/caolan/async. сделает код более читабельным и красивым. - person ElHacker; 15.12.2012
comment
@Logan Я бы не назвал это обходным путем, так устроен узел. - person PeterT; 08.03.2015
comment
возвращенный thing является «[object Promise]». Как мы можем прочитать данные внутри объекта обещания? (без обратного вызова .then) - person Rishitha Minol; 29.05.2018
comment
@RishithaMinol Вы можете await выполнить обещание из другой async функции: let thing = await getAThing(); - person JohnnyHK; 29.05.2018
comment
Я пытаюсь отойти от модели функции обратного вызова, где легко обработать ошибку или результат успеха, т.е. function(err, result) { if (err) { res.send(err) } else { // do things } - как вы обрабатываете/просматриваете ошибку, которая возвращается из let thing = await db.collection.findOne(query)? - person user1063287; 13.11.2018
comment
@user1063287 user1063287 С await вы должны использовать блоки try/catch для обработки ошибок. - person JohnnyHK; 13.11.2018
comment
@JohnnyHK - вот так? try { var bob = await getAThing() } catch(err) { console.log("mongodb query error is here: " + err ) } - person user1063287; 13.11.2018
comment
@ user1063287 Да. Дополнительные примеры см. здесь. - person JohnnyHK; 13.11.2018

ES 6 (узел 8+)

Вы можете использовать async/await.

Оператор await приостанавливает выполнение асинхронной функции до тех пор, пока обещание не будет разрешено и не вернет значение.

Таким образом, ваш код будет работать синхронно:

const query = MySchema.findOne({ name: /tester/gi });
const userData = await query.exec();
console.log(userData)



Старое решение — июнь 2013 г. ;)

Теперь доступен Mongo Sync, это правильный способ сделать синхронный запрос MongoDB в Node. js.

Я использую это для того же. Вы можете просто написать метод синхронизации, как показано ниже:

var Server = require("mongo-sync").Server;
var server = new Server('127.0.0.1');
var result = server.db("testdb").getCollection("testCollection").find().toArray();
console.log(result);

Примечание. Он зависит от node-fiber, и с ним возникают некоторые проблемы в Windows 8.

Удачного кодирования :)

person Amol M Kulkarni    schedule 07.06.2013
comment
Я закодировал 5-строчный скрипт с mongo-sync, и он потерпел неудачу, хотя он почти идеально соответствовал их тестовому коду. Кажется, есть ошибки. - person jcollum; 23.07.2013
comment
@jcollum: Не могли бы вы описать точную проблему, с которой вы столкнулись? потому что у меня он работает без серьезных проблем. Если вы уверены, что это ошибка в модуле, вы можете поднять новую проблему на Репозиторий - person Amol M Kulkarni; 24.07.2013
comment
Я представил ошибку. По-видимому, вам нужно удалить модули волокон из node_modules в библиотеке mongo-sync. Похоже на проблему с упаковкой. - person jcollum; 24.07.2013
comment
Если это зависит от node-fiber, то оно не синхронно - person Esailija; 18.04.2014

Хотя это не строго синхронно, шаблон, который я неоднократно использовал и находил очень полезным, заключается в использовании co. и promisify уступают асинхронным функциям. Для монго вы можете переписать приведенное выше:

var query = co( function* () {

    var db = new mongo.Db("mydatabase", server, {});
    db = promisify.object( db );
    db = yield db.open();

    yield db.authenticate("myuser", "mypassword");

    var collection = yield db.collection("Things");
    return yield collection.findOne( { name : "bob"} );

});

query.then( result => {

} ).catch( err => {

} );

Это означает:

  1. Вы можете написать «синхронный» код с любой асинхронной библиотекой.
  2. Ошибки выдаются из обратных вызовов, что означает, что вам не нужна проверка успеха
  3. Вы можете передать результат как обещание любому другому фрагменту кода.
person Hugheth    schedule 20.05.2016