3

Очень нужна ваша помощь. Вроде бы простой вопрос, но не могу понять как сохранить результат запроса к базе в переменную, используя mongoose. Предположим есть база данных test. Нужно запросить все записи из этой базы и сохранить в виде массива в переменной data.
Пробовал так:

var data = Test.find({}, (err,results) => {
  if(err) throw(err);
  console.log(results);
  return results;
});
console.log(data);

console.log(results) внутри функции печатает идельный массив. console.log(data) вне функции печатает огромный массив в котором есть масса инфрормации о структуре коллекции, описание запроса, но нет данных из БД.
Пробовал так:

function getResult() {
  Test.find({}, (err,result) => {
    if(err) throw(err);
    return result;
  })
};
var data = getResult();
console.log(data);

Результат - undefiened.
Помогите пожалуйста. Если объясните суть проблемы то буду вдвойне благодарен.

tumanoff
  • 45
  • 6

3 Answers3

4

Суть проблемы - вы ожидаете, что код выполняется сверху вниз, последовательно, то есть синхронно. А JS выполняется асинхронно. Чтобы получить последовательное выполнение асинхронного, нужно использовать async/await совместно с Promise и колбеками.

Это - теория. А теперь - практика. Ниже рабочий код, все разъяснения - в комментариях.

"use strinct";

const mongodb = require("mongodb"), assert = require("assert"), MongoClient = mongodb.MongoClient, config = { mongo: { uri: "mongodb://mongo:27017", db: "test_db", } };

class App {

constructor(config) { this.config = config;

// конструктор будет возвращать Promise, который можно "then()" и "catch()"
return new Promise(async (appResolve, appReject) => {

  try {
    // ждем подключение к Монге, оно также асинхронное
    await this.connectMongo();
    // инициализация (не обязательно)
    await this.initMongo();
    appResolve(this);
  } catch (err) {
    appReject(err);
  }

});

}

// эта часть взята с моего проекта // тут создаем коллекцию с уникальным полем email // разумеется, в Монге специально коллекции создавать не нужно, они создаются при первой вставке в них, // но тут добавляется уникальный индекс email async initMongo() { let collection = this.mongo.collection("mails"); await collection.createIndex( { "email": 1 }, { unique: true } );

// для примера добавляем один элемент
// replaceOne создает ИЛИ перезаписывает существующую запись,
// это я сделал для возможности повторного запуска скрипта без ошибок
// если использовать insertOne, то при втором запуске выскочит ошибка
// "duplicate key error collection"
let email = "foo@bar.tld";
await collection.replaceOne( { email: email }, { email: email, blocked: true }, { upsert: true } );

}

// асинхронное подключение, тут колбек подключения к Монге завернут в Promise, // при подключении выполняется resolve(); который яваскрипт ждет в строчке await this.connectMongo(); async connectMongo() { return new Promise((resolve, reject) => { MongoClient.connect(this.config.mongo.uri, { useNewUrlParser: true, keepAlive: 1, connectTimeoutMS: 5000 }, (err, db) => { assert.equal(null, err); this.mongo = db.db(this.config.mongo.db); resolve(); }); }); }

// а вот и "проблемная" функция, она асинхронная async getResult(email) { let collection = this.mongo.collection("mails"); return await collection.findOne({email: email.trim().toLowerCase()}); }

}

let api = new App(config); api.then(async (api) => { // тут вызывается асинхронная функция, и яваскрипт "ждет" на этой строчке let data = await api.getResult("foo@bar.tld"); // после завершения асинхронной функции данные в переменной уже будут console.log(data); }).catch(err => { // тут обработка ошибок console.log("API fatal error:", err); process.exit(1); });

UPD

Как я понял, используется express. Тогда код можно написать так:

// я поменял POST => GET для демонстрации
api.get('/test', async (req,res) => {
  // тут замените на свой клиент, я думаю это Resource
  let mongo = this.mongo;

let collection = mongo.collection("mails"); let data = await collection.findOne(); // данные будут отправлены в ответе сервера res.send(JSON.stringify(data)); });

Ключевое тут - async (req,res) и использование внутри await.

Total Pusher
  • 6,307
  • Спасибо огромное за ответ. Загнал Ваш код в js и запустил. В результате выдает замечательный { _id: 5f2235292064b70922defa5c, email: 'foo@bar.tld', blocked: true }. Но это же опять внутри функции api! Как мне использовать этот результат ниже? К примеру если я хочу собрать новый массив, и включить туда полученный документ и затем сохранить его в mongo? Я добавил свой код с описанием, внизу Вашего ответа. – tumanoff Jul 30 '20 at 02:58
  • @tumanoff см UPD. – Total Pusher Jul 30 '20 at 07:24
  • Заработало!!! Спасибо огромное! Надо бы разобраться с асинхронными операциями. – tumanoff Jul 30 '20 at 15:24
3
async function getResult() {
    const data = await Test.find({});
    console.log(data);
}

UPDATE

если Вы хотите вынести получения данных из mongoose в отдельную функцию (в чем я не особо вижу смысл), то попробуйте так:

// функция для получения данных с базы
const getResult = async () => await Test.find({});

// вызвать функцию и получить данные, но получать эти данные можно только внутри async функциях const data = await getResult();

Август
  • 1,605
  • Пробую запустить функцию: let result = getResult() и в результате получаю Promise { <pending> } – tumanoff Jul 29 '20 at 03:13
  • @tumanoff, всё верно. вариант с let result = getResult() не будет работать, потому что функция getResult рассчитана на то, чтобы получать данные из mongoose только внутри этой функции. – Август Jul 29 '20 at 18:25
  • Во-первых спасибо Вам за ответы. Надеюсь в итоге пойму всю глубину глубин. То есть чтобы получить результат от await getResult() мне нужно завернуть это в другую async function, я правильно понимаю? Но опять таки, результат будет доступен только внутри этой функции. Наверное я неправильно описал вопрос. Итак есть функция, которая делает запрос к БД, получает какие то данные внутри себя и потом нужно чтобы эти данные сохранились в переменную за пределами функции. Чтобы потом впоследствии можно было использовать эту переменную где то дальше в коде. – tumanoff Jul 29 '20 at 20:02
  • @tumanoff, да,чтобы получить результат от await getResult(), Вы должны вызвать эту функцию внутри асинхронной функции (async function). но это не обязательно, так как getResult() будет возвращать Promise и вы можете обработать его не с помощью await, а через .then() и тогда Вам не обязательно получать результат от getResult() внутри async функции – Август Jul 29 '20 at 23:03
1

Потому что операция асинхронная.

Думаю, можно так:

let data;
function getResult() {
  Test.find({}, (err,result) => {
    if(err) throw(err);
    data = result;
  })
};
kertAW
  • 2,443
  • Пробую console.log(data) после функции, все равно пишет что undefined :( – tumanoff Jul 28 '20 at 20:50