Node.js-等待循环内的所有承诺

时间:2019-04-23 08:08:26

标签: javascript node.js

我正在处理Node.js中的循环,该循环为循环的每次迭代执行两项任务。为简化起见,代码摘要如下:

  1. 从网页中提取产品元数据(阻止任务)。
  2. 将所有产品元数据保存到数据库(异步任务)。

保存操作(2)将在数据库中执行大约800次操作,并且不需要阻塞主线程(我仍然可以从网页中提取产品元数据)。

因此,话虽如此,等待产品保存没有任何意义。但是,如果我不等待就抛出承诺,则在循环的最后一次迭代中,Node.js进程将退出,并且所有未完成的操作都不会完成。

哪个是解决此问题的最佳方法?是否有可能在没有完成承诺或发射者计数器的情况下实现它?谢谢。

for (let shop of shops) {
  // 1
  const products = await extractProductsMetadata(shop);

  // 2
  await saveProductsMetadata(products);
}

2 个答案:

答案 0 :(得分:2)

将诺言收集到一个数组中,然后在其上使用Promise.all

 const storePromises = [];

 for (let shop of shops) {
    const products = await extractProductsMetadata(shop); //(1)
    storePromises.push(saveProductsMetadata(products)); //(2)
 }

 await Promise.all(storePromises);
 // ... all done (3)

通过(1)依次运行,(2)并行运行,然后(3)随后运行。

确保您还可以并行运行(1)和(2):

  await Promise.all(shops.map(async shop => {
    const products = await extractProductsMetadata(shop); //(1)
    await saveProductsMetadata(products);
 }));

如果其中一个承诺发生了错误,则可以使用try / catch块进行处理,以确保所有其他商店都不会受到影响:

 await Promise.all(shops.map(async shop => {
  try {
    const products = await extractProductsMetadata(shop); //(1)
    await saveProductsMetadata(products);
   } catch(error) {
     // handle it here
   }
 }));
  

如何向节点发送信号以完成该过程?

您可以手动调用process.exit(0);,但这掩盖了真正的问题:如果不再附加任何监听器,NodeJS将自动退出。这意味着在完成上面的代码之后,您应该关闭所有数据库连接/服务器/等等。

答案 1 :(得分:1)

我们正在创建要处理的数据包。当我们处理数据时,我们同步进行所有获取,并异步进行所有保存。

我还没有处理故障部分,我让您将其添加到其中。适当的try/catch或函数封装可以做到这一点。

/**
 * Call the given functions that returns promises in a queue
 * options = context/args
 */
function promiseQueue(promisesFuncs, options = {}, _i = 0, _ret = []) {
  return new Promise((resolve, reject) => {
    if (_i >= promisesFuncs.length) {
      return resolve(_ret);
    }

    // Call one
    (promisesFuncs[_i]).apply(options.context || this, options.args || [])
      .then((ret: any) => promiseQueue(promisesFuncs, _i + 1, options, [
        ..._ret,

        ret,
      ]))
      .then(resolve)
      .catch(reject);
  });
}

function async executePromiseAsPacks(arr, packSize, _i = 0) {
  const toExecute = arr.slice(_i * packSize, packSize);

  // Leave if we did execute all packs
  if (toExecute.length === 0) return true;

  // First we get all the data synchronously
  const products = await promiseQueue(toExecute.map(x => () => extractProductsMetadata(x)));

  // Then save the products asynchronously
  // We do not put await here so it's truly asynchronous
  Promise.all(toExecute.map((x, xi) => saveProductsMetadata(products[xi])));

  // Call next
  return executePromiseAsPacks(arr, packSize, _i + 1);
}

// Makes pack of data to treat (we extract synchronously and save asynchronously)
// Made to handle huge dataset
await executePromisesAsPacks(shops, 50);