简体   繁体   English

Node.js - 等待循环中抛出的所有promise

[英]Node.js - Await for all the promises thrown inside a loop

I'm dealing with a loop in Node.js that performs two tasks for every iteration of a loop. 我正在处理Node.js中的循环,它为循环的每次迭代执行两个任务。 To simplify, the code is summarized in: 为简化起见,代码总结如下:

  1. Extract products metadata from a web page (blocking task). 从网页中提取产品元数据(阻止任务)。
  2. Save all the products metadata to a database (asynchronous task). 将所有产品元数据保存到数据库(异步任务)。

The save operation ( 2 ) will perform about 800 operations in a database, and it doesn't need to block the main thread (I can still extracting products metadata from the web pages). 保存操作( 2 )将在数据库中执行大约800个操作,并且不需要阻止主线程(我仍然可以从网页中提取产品元数据)。

So, that being said, awaiting for the products to being saved doesn't have any sense. 所以,正如所说,等待产品被保存没有任何意义。 But if I throw the promises without awaiting for them, in the last iteration of the loop the Node.js process exits and all the pending operations are not finished. 但是如果我在没有等待它们的情况下抛出promise,则在循环的最后一次迭代中,Node.js进程退出并且所有未决操作都没有完成。

Which is the best approach to solve this? 哪种解决方法最好? Is it possible to achieve it without having a counter for finished promises or emitters? 是否有可能在没有完成承诺或发射器的计数器的情况下实现它? Thanks. 谢谢。

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

  // 2
  await saveProductsMetadata(products);
}

Collect the promises in an array, then use Promise.all on it: 收集数组中的promises,然后在其上使用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)

Through that (1) will run one after each other, (2) will run in parallel, and (3) will run afterwards. 通过(1)将一个接一个地运行,(2)将并行运行,(3)将在之后运行。

For sure you can also run (1) and (2) in parallel: 当然,您也可以并行运行(1)和(2):

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

And if an error occured in one of the promises, you can handle that with a try / catch block, to make sure all other shops won't be affected: 如果其中一个承诺发生错误,您可以使用try / catch块处理该错误,以确保所有其他商店不会受到影响:

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

how to signal node to finish the process ? 如何通知节点完成该过程?

You could manually call process.exit(0); 你可以手动调用process.exit(0); , but that hides the real problem: NodeJS exits automatically if there is no listener attached anymore . ,但这隐藏了真正的问题:如果没有连接侦听器, NodeJS会自动退出。 That means that you should close all database connections / servers / etc. after the code above is done. 这意味着您应该在上面的代码完成后关闭所有数据库连接/服务器/等。

We are creating packs of data to treat. 我们正在创建要处理的数据包。 When we treat the data, we do all the get synchronously, and all the save asynchronously. 当我们处理数据时,我们会同步执行所有get,并且异步保存所有内容。

I have not handled the failure part, I let you add it to it. 我没有处理失败部分,我让你添加它。 appropriate try/catch or function encapsulation will do it. 适当的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);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM