Сначала немного обзора. Node.js делает все сети неблокирующими и асинхронными. Это означает, что когда вы делаете сетевой запрос, он запускает операцию, немедленно возвращается и продолжает выполнять остальную часть вашего кода. Обратный вызов или обещание, связанное с этой асинхронной операцией, вызываются через некоторое время.
В вашем конкретном случае c весь ваш .forEach()
l oop выполняется до завершения, запуская все сетевые запросы в нем до того, как они закончили. Таким образом, вы выполняете console.log(booksArray.length);
до запуска любого из ваших обратных вызовов, и, следовательно, массив все еще пуст.
Каждый раз, когда вы хотите упорядочить или координировать более одной асинхронной операции в node.js, вы захотите использовать обещания. Он был встроен в язык уже пару лет, и это просто намного лучший способ программирования с асинхронными операциями. Я бы посоветовал вам найти несколько хороших руководств по работе с обещаниями и изучить их.
Затем используемая вами библиотека request()
устарела, переведена в режим обслуживания и не поддерживает обещания. Существует множество более современных альтернатив . Я лично использую библиотеку got()
, и это то, что я проиллюстрировал ниже.
Тогда я не знаю, какова ваша функция parseString()
, но она, очевидно, не использовать обещания и кажется асинхронным, поэтому, чтобы использовать его в рабочем процессе на основе обещаний, я «обещал» его, используя util.promisify()
, который встроен в node.js. Если у него есть интерфейс с обещаниями, вы должны просто использовать его напрямую.
Так или иначе, вот что я бы предложил:
const got = require('got');
const {promisify} = require('util');
const ps = promisify(parseString);
// this function returns a promise
async function getOverview(req, res) {
const books = await Book.find();
const booksArray = await Promise.all(books.map(book => {
const url =`https://www.goodreads.com/book/isbn/${book.isbn}key=${process.env.KEY}`;
let bookData = await got(url);
return ps(bookData);
}));
console.log(booksArray.length);
// make the booksArray be the resolved value of the promise returned
// by this async function
return booksArray;
}
Это делает все ваши сетевые вызовы параллельно, а затем использует Promise.all()
, чтобы указать, когда они все сделаны, и собрать все результаты по порядку.
Если по какой-то причине вы не можете выполнять все эти запросы параллельно (например, их так много, что объекты целевого сервера), то вы можете запускать их по одному тоже так:
const got = require('got');
const {promisify} = require('util');
const ps = promisify(parseString);
// this function returns a promise
async function getOverview(req, res) {
const books = await Book.find();
const booksArray = [];
for (let book of books) {
const url =`https://www.goodreads.com/book/isbn/${book.isbn}key=${process.env.KEY}`;
let bookData = await got(url);
let data = await ps(bookData);
booksArray.push(data);
}
console.log(booksArray.length);
// make the booksArray be the resolved value of the promise returned
// by this async function
return booksArray;
}
И, поскольку это async
функции, они возвращают обещание, поэтому вызывающий должен использовать await
или .then()
для получения значения из обещания.
getOverview(...).then(results => {
console.log(results);
}).catch(err => {
console.log(err);
});
Если вы действительно намереваетесь передать req
и res
в эту функцию и использовать их там, то вам, вероятно, потребуется локальная обработка ошибок, поэтому, если какая-либо из ваших операций await
отклоняется, вы можете перехватить эту ошибку и отправить ошибку ответ. Вы сделали бы это с try/catch
внутри тела функции. Вероятно, вы можете просто использовать один верхний уровень try/catch
и затем отправить ответ об ошибке в обработчике catch
.