【问题标题】:Nodejs and Mongoose: Await stops loopNodejs 和 Mongoose:等待停止循环
【发布时间】:2020-04-17 19:33:35
【问题描述】:

我正在解析 CSV 并处理每条记录以使用 Mongoose 将其插入到我的 MongoDB 中。我已经正确地从 CSV 中得到了一个数组,但是当我开始使用 forEach(和 async/await)对其进行迭代时,它就停在那里了。

这是我的代码:

const csv = require('csv-parser');
const fs = require('fs');
const Customer = require('../../app/models/Customers');

const projects = [];

const processRecords = async () => {
  try {
    const usableProjects = projects.filter((project) => project['cust_number customer']);
    const customerNames = [...new Set(usableProjects.map((item) => item['Parent Name']))];
    await customerNames.forEach(async (customerName) => {
      console.log('Point 1');
      const existingCustomer = await Customer.find({Name: customerName});
      console.log('Point 2'); //<======= THIS CODE IS NEVER REACHED
      if (existingCustomer.length > 0) {
        console.log(`${customerName} already exists. Skipping...`);
        return;
      }
      const customerRecord = usableProjects.find((project) => project['Parent Name'] === customerName);
      const newCustomer = {
        Name: customerName,
        Source: 'CSV',
        Initials: customerRecord.Initials,
        Code: customerRecord.Codename,
      };
      const newCustomerRecord = await Customer.create(newCustomer);
      if (newCustomerRecord) {
        console.log(`Customer ${newCustomerRecord._id} created`);
      }
    });
  } catch (err) {
    console.log(err);
  }
};

fs.createReadStream('customer_table.csv')
  .pipe(csv())
  .on('data', async (data) => projects.push(data))
  .on('end', async () => {
    processRecords();
  });

这是输出:

Point 1
Point 1
Point 1
Point 1
Point 1
Point 1
Point 1
Point 1
Point 1
Point 1
Point 1
Point 1
Point 1

我知道这可能与我未处理的同步/异步代码有关。但我无法修复它。提前致谢。

【问题讨论】:

  • array forEach 不返回 Promise,所以 awaiting 它不会做你想做的事 - 最简单和最简洁的解决方案是使用常规 for 循环而不是 forEach - 但是,这可能不解释为什么Customer.find 永远不会解决 - 但这是朝着正确方向迈出的一步
  • 这能回答你的问题吗? Using async/await with a forEach loop

标签: javascript node.js mongodb mongoose async-await


【解决方案1】:

首先,让我们将try-catch 放在Customer.find() 上 (为了清楚起见,我会简化你的代码。我会用f() 替换Customer.find()

async function success() { return "Hurrah!" }
async function failure() { throw new Error("Oops!") }

const customerNames = [1, 2, 3]

const processRecords1 = async (f) => {
    try {
        await customerNames.forEach(async (customerName) => {
            try {
                console.log('Point 1');
                const existingCustomer = await f()
                console.log('Point 2', existingCustomer);
                // ...
            } catch (err) {
                console.log('Point 3', err);
            }
        });
    } catch (err) {
        console.log('Point 4', err);
    }
};
setTimeout(() => processRecords1(success), 0);

输出:

Point 1
Point 1
Point 1
Point 2 Hurrah!
Point 2 Hurrah!
Point 2 Hurrah!

如您所见,如果f = success 达到“第 2 点”。所以这是第一个问题:您的 Customer.find() 失败并且您没有看到异常。 让我们尝试从f() 抛出,只是为了证明这一点......

setTimeout(() => processRecords1(failure), 100);

输出:

Point 1
Point 1
Point 1
Point 3 Error: Oops!
Point 3 Error: Oops!
Point 3 Error: Oops!

是的,如果f() 失败,我们永远不会到达“第 2 点”。但现在我们确实在“第 3 点”中看到了一个错误。所以我们可以在这里停下来。

但是让我们尝试在processRecords() 的顶层捕获异常并到达“第 4 点”。 正如已经提到的,forEach() 不返回值。让我们试试map()

const processRecords2 = async (f) => {
    try {
        await customerNames.map(async (customerName) => {
            console.log('Point 1');
            const existingCustomer = await f()
            console.log('Point 2', existingCustomer);
            // ...
        });
    } catch (err) {
        console.log("Point 4", err);
    }
};
setTimeout(() => processRecords2(failure), 200);

输出:

Point 1
Point 1
Point 1
Uncaught (in promise) Error: Oops!
Uncaught (in promise) Error: Oops!
Uncaught (in promise) Error: Oops!

没有运气。这是因为map() 确实返回了一个值,但它是Array,而不是Promise。你不能await 用于 Promise-s 数组,但你可以使用 Promise.all() https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

const processRecords3 = async (f) => {
    try {
        await Promise.all(customerNames.map(async (customerName) => {
            console.log('Point 1');
            const existingCustomer = await f()
            console.log('Point 2', existingCustomer);
            // ...
        }));
    } catch (err) {
        console.log("Point 4", err);
    }
};
setTimeout(() => processRecords3(failure), 300);

输出:

Point 1
Point 1
Point 1
Point 4 Error: Oops!
Point 4 Error: Oops!
Point 4 Error: Oops!

那个。将 await customerNames.forEach(...) 替换为 await Promise.all(customerNames.map(...) 即可。

【讨论】:

    【解决方案2】:

    我同意@JaromandaX,forEach 不支持承诺,并且不支持异步/等待。请改用map

    【讨论】:

      【解决方案3】:

      Array forEach 不返回任何内容,因此您不能等待它们。如果你想等待一系列的承诺,map 是要走的路。但是,请注意,您应该将其与 Promise.all

      async function example(arr) {
        await Promise.all(
          // Assume item.fetch() returns a promise
          arr.map(item => item.fetch())
        );
      }
      

      现在,这将并行运行所有 item.fetch 调用。这是大多数情况下的首选方式。但是,如果您想连续运行 item.fetch 调用,则应使用 for 循环。

      async function example(arr) {
        for (let item of arr) {
          await item.fetch();
        }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-10-18
        • 2014-07-19
        • 2021-09-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-08-03
        • 2020-01-31
        相关资源
        最近更新 更多