Сохранить значение then в переменной вне обещания

Я новичок в концепции промисов и пытаюсь понять, как работают области действия. Я в основном пытаюсь сохранить значение внутри тогда() в переменной за пределами Обещать

Ниже приведена простая функция, которую я написал в Nodejs (Express), используя Sequelize для запуска запроса в БД.

exports.getTest = (req, res, next) => {
    var categories = [];
    var names = ['Category 1', 'Category 2', 'Category 3', 'Category 4'];
    for (var i = 0; i < names.length; i++) {
        model.Category.findOne({
            where: {
                name: names[i]
            },
            attributes: ['id']
        }).then(id => {
            categories.push(
            {
                category_id: id.id
            });
        });
    }
    res.json(categories);
}

У меня есть другая логика, которую нужно запустить после этого, и у меня есть цикл for вокруг обещания. Итак, я не могу запустить следующую логику внутри then, иначе она будет выполняться несколько раз из-за цикла for. Мне нужно заполнить массив категории, чтобы использовать его в моей следующей операции.

В настоящее время мой ответ (res.json(categories)) []

Любая помощь будет оценена по достоинству.

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

Заранее спасибо!

Вы пытались использовать async/await?

Lordie 28.05.2019 20:40
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
1
7 247
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

В вашем случае categories всегда будет возвращаться [], потому что вы не ждете, пока все ваши обещания закончатся, прежде чем вернуть свой ответ. Циклы for не ждут завершения асинхронных действий, прежде чем перейти к следующей итерации. Поэтому цикл завершается, и ответ возвращается до завершения любого из них.

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

Вот как это должно выглядеть

exports.getTest = () => {
    var categories = [];
    var names = ['Category 1', 'Category 2', 'Category 3', 'Category 4'];
    var promiseArray = [];
    for (var i = 0; i < names.length; i++) {
        promiseArray.push(
          model.Category.findOne({
              where: {
                  name: names[i]
              },
              attributes: ['id']
          }).then(id => {
              categories.push(
              {
                  category_id: id.id
              });
          });
        )
    }

    return Promise.all(promiseArr)
}

getTest() теперь возвращает обещание, поэтому его можно назвать так

getTest()
  .then(data => {
    // data will be an array of promise responses
  }).catch(err => {
    console.info(err);
  })

Привет! Большое спасибо. Это работает отлично. Хотя ваш ответ был точным, ответ Джимми помог мне понять концепцию и ее ход. С этим пониманием я смог изменить код в соответствии со своими требованиями. Но большое спасибо за ваш ответ!

ashishsanjayrao 29.05.2019 08:01
exports.getTest = (req, res, next) => {
    var categories = [];
    var names = ['Category 1', 'Category 2', 'Category 3', 'Category 4'];
    names.forEach(name => {
        Category.findOne({where: {name: name}}).then(category => {
            categories.push({category_id: category.id})
        })
    })
    res.json(categories);
}

Таким образом, model.findeOne() возвращает обещание с объектом первой категории с каждым из имен. then() перехватывает это обещание, разрешает его, и вы даете ему функцию обратного вызова, которая передает этот объект в качестве параметра.

Это может выглядеть примерно так

Categories.findOne({where: {name: name}).then(function(category){
    // do something with that category
})

Но функция стрелки делает его более читабельным, так как then(category => {//some код}).

ForEach можно использовать почти таким же образом, где вместо записи categories.forEach(function(category){ //do something }) мы используем стрелочную функцию, и каждая категория в этом массиве будет передана в качестве аргумента вашей функции.

Liondj 28.05.2019 20:14

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

Isaac Vidrine 28.05.2019 20:26
Ответ принят как подходящий

Вы можете попробовать Promise.all()

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

The Promise.all() method returns a single Promise that resolves when all of the promises passed as an iterable have resolved or when the iterable contains no promises. It rejects with the reason of the first promise that rejects.

var getTest = (req, res, next) => {
    var categories = [];
    var promises = [];
    var names = ['Category 1', 'Category 2', 'Category 3', 'Category 4'];
    var resolveCount = 0;
    for (var i = 0; i < names.length; i++) {
        // Store the name in variable so that can be persistent and not
        // affected by the changing 'i' value
        const name = names[i]
        promises.push(new Promise((resolve, reject) => {
        
          // Your DB calls here. We shall use a simple timer to mimic the
          // effect
          setTimeout(() => {
            categories.push(name)
            resolveCount++;
            resolve();
          }, 1000)
        }));
    }
    
    
    Promise.all(promises).then(function() {
      console.info("This should run ONCE before AFTER promise resolved")
      console.info("resolveCount: " + resolveCount)
      console.info(categories);
      
      // Do your logic with the updated array
      // res.json(categories);
    });
    
    console.info("This will run immediately, before any promise resolve")
    console.info("resolveCount: " + resolveCount)
    console.info(categories)
}

getTest();

Привет! Большое спасибо. Это сработало отлично. Как и решение Исаака. Однако вы инкапсулировали вызовы БД в другой промис new Promise((resolve, reject). А в решении Исаака я напрямую добавляю вызовы БД (обещания) в массив обещаний. Что это меняет? Хотя результаты точно такие же.

ashishsanjayrao 29.05.2019 07:57

Функция model.Category.findOne(..).then() возвращает Promise, так что вы можете просто поместить результат этой функции в массив. Однако для моего примера я создал несколько новых промисов, чтобы проиллюстрировать общую идею.

jimmy5312 29.05.2019 08:18

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