Я новичок в концепции промисов и пытаюсь понять, как работают области действия. Я в основном пытаюсь сохранить значение внутри тогда() в переменной за пределами Обещать
Ниже приведена простая функция, которую я написал в 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: я знаю, что это обычная тема, но, как я уже упоминал, я новичок в этом, и другие ответы не соответствовали моему сценарию и еще больше меня сбивали с толку.
Заранее спасибо!
В вашем случае 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);
})
Привет! Большое спасибо. Это работает отлично. Хотя ваш ответ был точным, ответ Джимми помог мне понять концепцию и ее ход. С этим пониманием я смог изменить код в соответствии со своими требованиями. Но большое спасибо за ваш ответ!
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 })
мы используем стрелочную функцию, и каждая категория в этом массиве будет передана в качестве аргумента вашей функции.
Это вообще не решает проблему, это почти тот же код, что и у OP, за исключением того, что вместо цикла for вы используете .forEach
Вы можете попробовать 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)
. А в решении Исаака я напрямую добавляю вызовы БД (обещания) в массив обещаний. Что это меняет? Хотя результаты точно такие же.
Функция model.Category.findOne(..).then() возвращает Promise, так что вы можете просто поместить результат этой функции в массив. Однако для моего примера я создал несколько новых промисов, чтобы проиллюстрировать общую идею.
Вы пытались использовать async/await?