В вашем коде есть несколько вещей, которые нужно решить, поэтому давайте пойдем шаг за шагом:
dbresult
выглядит как массив, судя по использованию dbresult[i]
, но у вас также есть условие i < dbresult
, которое подразумевает, что это целое число. Я собираюсь предположить, что вы имели в виду i < dbresult.length
.
- Вы используете
new Promise(...)
в ситуации, когда вы уже имеете дело с обещаниями. Вы не должны никогда использовать этот шаблон, если у вас нет альтернативы, и всегда пытаться связать .then
вызовы и вернуть их результаты (которые также всегда являются обещаниями).
- Похоже, вы не поняли, что обратный вызов, переданный
.then
, всегда будет выполняться асинхронно после выполнения остальной части кода. По этой причине ваш объект выходит пустым: функция resolve
вызывается до того, как успевает выполнить любой из запросов.
Теперь, циклы и обещания не очень хорошо сочетаются, но есть способы справиться с ними. Что вам нужно понять, так это то, что с помощью циклов вам нужно цепочка обещаний . Таким образом, существует два способа объединения обещаний: императивный и функциональный.
Я собираюсь сосредоточиться на функции parseText
и опустить ненужные детали. Это то, что вы бы сделали для полного императивного решения:
function parseText (dbresult) {
// although the contents of the object change, the object doesn't,
// so we can just use const here
const textObj = {};
// initialize this variable to a dummy promise
let promise = Promise.resolve();
// dbresult is an array, it's clearer to iterate this way
for (const result of dbresult) {
// after the current promise finishes, chain a .then and replace
// it with the returned promise. That will make every new iteration
// append a then after the last one.
promise = promise
.then(() => Mercury.parse(result.url, {...}))
.then((response) => (textObj[result.id] = response.excerpt));
}
// in the end, the promise stored in the promise variable will resolve
// after all of that has already happened. We just need to return the
// object we want to return and that's it.
return promise.then(() => textObj);
}
Надеюсь, комментарии помогут. Опять же, работа с обещаниями в цикле - отстой.
Есть два способа сделать это более простым способом! оба используют функциональные методы массивов. Первый самый простой, и я рекомендую его, если массив не будет очень большим. Он использует .map
и Promise.all
, двух могущественных союзников:
function parseText (dbresult) {
const textObj = {};
// create an array with all the promises
const promises = dbresult.map(result => Mercury.parse(result.url, {...})
.then((response) => (textObj[result.id] = response.excerpt)))
);
// await for all of them, then return our desired object
return Promise.all(promises).then(() => textObj);
}
Примечание: bluebird
пользователи могут сделать это еще лучше, используя Promise.map
и передав значение concurrency
. На самом деле это, на мой взгляд, лучшее решение, но я хочу придерживаться ванили здесь.
Основная проблема этого решения, однако, заключается в том, что все запросы будут запущены сразу . Это может означать, что для очень больших массивов некоторые запросы просто ждут в очереди, или вы исчерпываете лимит сокета процесса, в зависимости от реализации. В любом случае, это не идеально, но для большинства ситуаций будет работать.
Другое функциональное решение состоит в репликации императивного решения с использованием .reduce
вместо цикла for ... of
, и оно реализовано в конце ответа, скорее как любопытство, чем что-либо еще, поскольку я думаю, что это слишком "умный код".
На мой взгляд, лучший способ решить эту проблему - просто использовать async/await
и вообще забыть обещания . В этом случае вы можете просто написать свой цикл в обычном порядке и, если нужно, просто поставить await:
async function parseText (dbresult) {
const textObj = {};
for (const result of dbresult) {
// here just await the request, then do whatever with it
const response = await Mercury.parse(result.url, {...}))
textObj[result.id] = response.excerpt;
}
// thanks to await, here we already have the result we want
return textObj;
}
Вот и все, это так просто.
А теперь для того, что я считаю «умным» решением, используя только .reduce
:
function parseText (dbresult) {
const textObj = {};
return dbresult.reduce(
(prom, result) => prom
.then(() => Mercury.parse(result.url, {...}))
.then((response) => (textObj[result.id] = response.excerpt)),
Promise.resolve()
).then(() => textObj);
}
Если не сразу понятно, что он делает, это нормально. Это делает то же самое, что и исходный императив then
- цепочка, просто используя .reduce
вместо ручной for
петли.
Обратите внимание, что лично мне не обязательно делать это, так как я думаю, что это слишком "умно" и требует умственного разбора. Если реализация чего-то подобного (then
-цепь с использованием .reduce
является невероятно полезной, даже если немного запутанной), пожалуйста, добавьте комментарий, объясняющий, почему вы это сделали, что это такое или что-то, что может помочь другие разработчики понимают их с первого взгляда.