【问题标题】:Loop through an array to generate a new array. It returns 0 at the end of the loop循环遍历数组以生成新数组。它在循环结束时返回 0
【发布时间】:2020-05-03 17:17:56
【问题描述】:

数据库中有 4900 本书。我希望在循环结束时在新数组中拥有相同数量的书籍。但是,booksArray 的长度为零。可能是什么问题和可能的解决方案?

const getOverview = async(req, res) => {

    const books = await Book.find();

    const booksArray = new Array();

    books.forEach(book => {

        const url =`https://www.goodreads.com/book/isbn/${book.isbn}key=${process.env.KEY}`;

        request.get(url).then(result => {

            parseString(result, (error, goodReadsResult) => {

                const goodreadsBook = goodReadsResult.GoodreadsResponse.book[0];

                booksArray.push(goodreadsBook);

            })
        });
    })
    console.log(booksArray.length);
};

【问题讨论】:

  • request() 是非阻塞和异步的。这意味着回调发生在循环完成后很长时间。因此,您正在尝试在填充数组之前使用它。
  • 如何在没有 request() 的情况下向 goodreads API 发出同步请求?
  • parseString() 是做什么的?你能包括那个代码吗?

标签: node.js loops foreach


【解决方案1】:

首先是一个概述。 Node.js 将所有网络都作为非阻塞和异步的。这意味着当您发出网络请求时,它会启动操作并立即返回并继续执行其余代码。稍后会调用与该异步操作相关的回调或承诺。

在您的具体情况下,您的整个 .forEach() 循环运行完成,在其中任何一个完成之前启动其中的所有网络请求。因此,您在任何回调运行之前都在执行console.log(booksArray.length);,因此数组仍然是空的。

任何时候你想在 node.js 中对多个异步操作进行排序或协调,你都需要使用 Promise。它已经内置到语言中已有几年了,它只是一种使用异步操作进行编程的更好的方法。我建议你找到一些关于 Promise 如何工作的好教程并学习它们。

那么,你正在使用的库request() 是旧的并且已经进入维护模式并且不支持承诺。还有各种modern alternatives。我个人使用got() library,这就是我在下面说明的内容。

然后,我不知道你的 parseString() 函数是什么,但它显然不使用承诺并且似乎是异步的,所以为了在基于承诺的工作流程中使用它,我“承诺”它使用 node.js 内置的util.promisify()。如果它有一个 Promise 接口,你应该直接使用它。

不管怎样,这就是我的建议:

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 函数,它们返回一个promise,因此调用者必须使用await.then() 从promise 中获取值。

getOverview(...).then(results => {
    console.log(results);
}).catch(err => {
    console.log(err);
});

如果您确实打算将 reqres 传递到该函数并在那里使用它们,那么您可能也需要本地错误处理,因此如果您的任何 await 操作被拒绝,您可以捕获该错误并发送错误响应。您可以在函数体内使用 try/catch 来执行此操作。您可能只使用一个顶级try/catch,然后在catch 处理程序中发送错误响应。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-06-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-10
    相关资源
    最近更新 更多