Сохранение результата запроса к mongodb в переменную

Очень нужна ваша помощь. Вроде бы простой вопрос, но не могу понять как сохранить результат запроса к базе в переменную, используя 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.
Помогите пожалуйста. Если объясните суть проблемы то буду вдвойне благодарен.


Ответы (3 шт):

Автор решения: Voprositel

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

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

let data;
function getResult() {
  Test.find({}, (err,result) => {
    if(err) throw(err);
    data = result;
  })
};
→ Ссылка
Автор решения: Август
async function getResult() {
    const data = await Test.find({});
    console.log(data);
}

UPDATE

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

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

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

NEXT UPDATE Во-первых спасибо Вам за ответы. Надеюсь в итоге пойму всю глубину глубин. То есть чтобы получить результат от await getResult() мне нужно завернуть это в другую async function, я правильно понимаю? Но опять таки, результат будет доступен только внутри этой функции. Наверное я неправильно описал вопрос. Итак есть функция, которая делает запрос к БД, получает какие то данные внутри себя и потом нужно чтобы эти данные сохранились в переменную за пределами функции. Чтобы потом впоследствии можно было использовать эту переменную где то дальше в коде.

→ Ссылка
Автор решения: Total Pusher

Суть проблемы - вы ожидаете, что код выполняется сверху вниз, последовательно, то есть синхронно. А 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 = "[email protected]";
    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("[email protected]");
  // после завершения асинхронной функции данные в переменной уже будут
  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.

→ Ссылка