Как по трем моделям найти необходимую новость?

Передаю данные на бекенд(обычный урл строку) и обрабатываю таким образом:

const Tag = require('../models/tag.model')
const Card = require('../models/card.model')
const Post = require('../models/post.model')

module.exports.getData = async (req, res) => {
    try {
        const tag = await Tag.findOne({tag: req.params.data})
        const card = await Card.findOne({card: req.params.data})
        const post = await Post.findOne({post: req.params.data})
        if(tag) {
            res.json(tag)
        } else if(card) {
            res.json(card)
        } else if(post) {
            res.json(post)
        }
    } catch(e) {
        res.status(500).json(e)
    }
}

После обработки одна из моделей находит у себя статью/новость и передают найденную новость обратно. Все работает, но может есть более красивый способ обработать?


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

Автор решения: Alexander Yukal

Применив фукциональное прграммирование, ваш код можно улучшить добавлением еще одной функции, которая в качестве аргументов может принимать неограниченное количество моделей. Использовать этот метод можно таким способом:

// в самом первом аргументе будут находится входные данные для моделей
// в данном случае это req.params.data
// все остальные аргументы являются моделями (Tag, Card, Post, ...)
findInModels(data, Tag, Card, Post, Model4, ...ModelN)

След шагом, я бы улучшил этот участок кода:

const tag = await Tag.findOne({tag: req.params.data});
const card = await Card.findOne({card: req.params.data});
const post = await Post.findOne({post: req.params.data});

Многие из разработчиков понимают как удобен async/await и для чего он нужен, но не многие понимают что иногда применив несколько await в одном методе мы делаем шаг назад от идеологии асинхронного программирования. Если обратиться к документации async/await и еще раз внимательно сфокусироваться на фразе

Функция async может содержать выражение await, которое приостанавливает выполнение функции async и ожидает ответа

и теперь после прочитанного сфокусироваться на нашем коде, можно проанализировать след. выполнение:

await // стоп. интерпретатор ожидает результата
      // вернул результат, интерпретатор пошел дальше

await // стоп. интерпретатор ожидает результата
      // вернул результат, интерпретатор пошел дальше

await // стоп. интерпретатор ожидает результата
      // вернул результат, интерпретатор пошел дальше

Улучшить это можно довольно просто, с помощью Promise объекта. В зависимости от стратегии поиска, вы можете применить один из этих вариантов поиска необходимого результата:

В случае если вам нужно вернуть тоько первый удачный вариант, вам подойдет Promise.any(), ну а если вам важно получить все результаты, тогда подойдет Promise.allSettled() или Promise.all(). А теперь давайте рассмотрим варианты улучшения.

const promises = [
  Tag.findOne({tag: req.params.data}),
  Card.findOne({card: req.params.data}),
  Post.findOne({post: req.params.data})
];

const response = await Promise.any(promises);
const response = await Promise.allSettled(promises);

Теперь когда мы получим результат, response будет массивом этих результатов и чтобы не выстраивать многоэтажные if выражения, мы можем отфильтровать наши данные с помощью filter таким образом:

// здесь я не уверен в том, что вам должно прити в качестве пустого 
// результата, я проверяю на undefined, но вы можете заменить это правило
// на свое усмотрение добавляя логические сопоставления && и ||.
const articles = response.filter((item) => item !== undefined);

Так как filter всегда возвращает массив, нам осталось проверить наличие содержания и вывести результат. В вашем примере нет обработки запроса в случае когда в качестве результатов данные приходят пустые. Проанализируем еще раз ваш пример кода, в частности этот участок:

// теперь представьте что вам пришел пустой результат. Тоесть по 
// входным данным, которые вы передавали вначале req.params.data,
// по ним ничего небыло найдено. В результате tag, card и post
// пришли пустые. Как думаете что отправится в качестве результата?

if(tag) {
  // это правило не выполнится, данные пустые
  res.json(tag);
} else if(card) {
  // это правило не выполнится, данные пустые
  res.json(card);
} else if(post) {
  // это правило не выполнится, данные пустые
  res.json(post);
}

// В этой точке интерпретатор достиг дна логического блока,
// а в возврата нет. Ваш клиент призадумается как мин. на 30 сек. 
// (в зависимости от настроек) пока не вернет ошибку о том что сервер
// не отвечает.

Поэтому я предлагаю исправить это вернув пустой результат null:

const article = articles.length ? articles.shift() : null;
res.json(article);

Реализация всего кода:

const Tag = require('../models/tag.model');
const Card = require('../models/card.model');
const Post = require('../models/post.model');

function findInModels() {
  // входные аргументы достаем из объекта arguments и преобразуем в массив
  const args = [...arguments];

  if (args.length < 2) {
    // Должно быть как минимум 2 аргумента
    const result = Promise.reject('There must be at least 2 arguments');

    // по контракту наш метод должен возвращать список промис объектов
    return [result];
  }

  // извлекаем самый первый аргумент (это входные данные для моделей)
  // в предыдущей версии это былы данные req.params.data
  const value = args.shift();

  return args.map((model) => {
    // для начала нам нужно узнать имя модели, чтобы построить параметры.
    // для этого мы вытаскиваем имя конструктора и переводим в нижний регистр
    // я надеюсь что ваши модели в оригинале так и називаются как и в примере
    const modelName = model.constructor.name.toLowerCase();

    // создаем параметр { tag: data } или { card: data } или { post: data }
    // подробней о defineProperty смотрите на MDN.
    const queryParams = Object.defineProperty({}, modelName, { value });

    return model.findOne(queryParams);
  });
}

module.exports.getData = async (req, res) => {
  const { data } = req.params;

  try {
    const promises = findInModels(data, Tag, Card, Post);
    const response = await Promise.allSettled(promises);
    const articles = response.filter((article) => article !== undefined);

    const article = articles.length ? articles.shift() : null;

    return res.json(article);
  } catch (e) {
    res.status(500).json(e);
  }
};

Подробнее об Object.defineProperty

→ Ссылка
Автор решения: Dmitry
module.exports.getData = async (req, res) => {
  const {data} = req.params

  try {
    const promises = [
      Tag.findOne({tag: data}),
      Card.findOne({card: data}),
      Post.findOne({post: data})
    ]
    const response = await Promise.allSettled(promises)
    const article = response.filter(item => item.value !== null)
    // const article = articles.length ? articles.shift() : null
    console.log(article[0].value)
    return res.json(article[0].value.category)
  } catch (e) {
    res.status(500).json(e)
  }
}

Вот так получилось реализовать только. Как-то коряво.

→ Ссылка