【问题标题】:How to wait for a promise inside a loop and promise function如何在循环和承诺函数中等待承诺
【发布时间】:2020-05-15 06:22:15
【问题描述】:

我正在尝试在循环中等待多个承诺。此循环必须转 3 次发送 3 个产品数组。两个初始循环将分别发送 1000 个产品,第三个循环将发送 500 个。

事实是该函数忽略了第三轮而不是将其发送到数据库,正如您在该日志中看到的那样,仅添加了 2000 个产品

我的方法

const MAXIMUM_POST_SIZE = 1000
export const addProducts = (products) => new Promise(
  (resolve, reject) => {
    for (let i = 0; i < Math.ceil(products.length / MAXIMUM_POST_SIZE); i += 1) {
      const indexToStartExtraction = i * MAXIMUM_POST_SIZE;
      const sizeOfExtraction = indexToStartExtraction + MAXIMUM_POST_SIZE;
      const extractedProducts = products.slice(indexToStartExtraction, sizeOfExtraction);
      const ref = firebase.ref('products');
      const normalizedPostData = arrayToJsonObjectWithUid({ products: extractedProducts, ref });

      console.log(extractedProducts.length);

      ref.update(normalizedPostData).then(() => {
        console.log(`Doing ${i} call`);
        resolve(true);
      })
        .catch((err) => reject(err));
    }
  },
);

我试过没有成功:

export const addProducts = (products) => new Promise(
  (resolve, reject) => {
    const promises = [];

    for (let i = 0; i < Math.ceil(products.length / MAXIMUM_POST_SIZE); i += 1) {
      const indexToStartExtraction = i * MAXIMUM_POST_SIZE;
      const sizeOfExtraction = indexToStartExtraction + MAXIMUM_POST_SIZE;
      const extractedProducts = products.slice(indexToStartExtraction, sizeOfExtraction);
      const ref = firebase.ref('products');
      const normalizedPostData = arrayToJsonObjectWithUid({ products: extractedProducts, ref });

      console.log(extractedProducts.length);

      promises.push(ref.update(normalizedPostData));
    }
    Promise.all(promises)
      .then(() => resolve())
      .catch(() => reject());
  },
);

如何正确等待每个承诺?

【问题讨论】:

  • 基本上,您不能在同步函数的循环中等待,但您可以将执行 API 调用的 Promise 和在特定时间后解析的 Promise 链接起来,并拥有一个异步函数来执行您的操作想要。
  • 这与您的问题无关,但我建议您将数组分块,而不是您在这里执行 for 循环的方式。 lodash has a chunk function。这是做什么的,如果你给它一个[1,2,3,4,5,6,7]的数组,并告诉它把它分成3个,它会给你[[1,2,3],[4,5,6],[7]],然后你可以遍历分块的数组,将你的元素分组到数组中大小合适。
  • 在 Stack Overflow 上,请不要显示文字图片。将文本复制到问题本身,以便于阅读和搜索。
  • "...不违反 eslint 的规则..." no-await-in-loops 不是“推荐”规则之一,从rule list 可以看出。如果您阅读rule documentation,它特别指出它试图保护您免受无意以串行方式而不是并行方式进行操作。由于您想按顺序进行操作(据我了解这个问题),因此该规则不适用。如果您有一个不寻常的规则集启用它,请使用代码注释为此代码禁用它。
  • 为了清楚起见:您是要并行运行这些更新(全部同时运行)还是串行运行(一个接一个地运行)?如果您希望它们并行运行,您现有的带有Promise.all 的代码应该可以正常工作。当然,您必须等待addProducts 的承诺解决。

标签: javascript firebase promise


【解决方案1】:

我的理解是您希望串联运行它们。您当前使用 Promise.all 的代码将并行运行它们。

如果您想连续运行它们,最简单的方法(因为您使用的是 Node.js)是使用 async 函数和 await:

const MAXIMUM_POST_SIZE = 1000
export const addProducts = async (products) => {
  for (let i = 0; i < Math.ceil(products.length / MAXIMUM_POST_SIZE); i += 1) {
    const indexToStartExtraction = i * MAXIMUM_POST_SIZE;
    const sizeOfExtraction = indexToStartExtraction + MAXIMUM_POST_SIZE;
    const extractedProducts = products.slice(indexToStartExtraction, sizeOfExtraction);
    const ref = firebase.ref('products');
    const normalizedPostData = arrayToJsonObjectWithUid({ products: extractedProducts, ref });

    console.log(extractedProducts.length);

    console.log(`Doing ${i} call`);
    await ref.update(normalizedPostData);
  }
  return true;
};

任何最新版本的 Node.js 都支持 async 函数。

该函数返回一个承诺:

  • 如果任何ref.update 呼叫被拒绝,则拒绝,或者
  • 当所有这些都成功完成时,使用值 true 完成

在开始下一个之前,它会等待每个完成。相比之下,您的带有Promise.all 的版本会启动所有更新,让这些更新同时运行,然后等待它们全部完成。

请注意,调用 addProducts 的任何内容都需要等待其承诺完成,除非 addProducts 调用之后的任何内容可以与更新调用 addProducts 开始同时运行。

【讨论】:

  • 当我这样做时,我收到了来自 eslint 的警告:在 loop.eslint(no-await-in-loop) 中出现意外的await。我想在不违反 eslint 规则的情况下做到这一点
  • @MuriloMedeiros - ESLint 的规则不是一成不变的,no-await-in-loop 不是他们推荐的规则之一(例如:通常它是关闭的)。如果您想以串行方式(而非并行方式)进行这些调用,在 async 函数的循环中使用 await 绝对没问题。
  • @MuriloMedeiros - 事实上,如果你阅读rule documentation,它会说规则的原因是你得到并行操作。由于您的目标是避免并行操作...
【解决方案2】:

我认为您不应该使用await,因为所有请求都是相互独立的,并且可以并行运行,因此您的第二个示例应该是最好的路线。

这是一个简单的 for 循环进行异步操作的 sn-p:

function someAsyncFn (i) {
  return new Promise ((resolve) => setTimeout(() => {
    console.log(i)
  }, 100))
}

const promises = []
for (let i=0; i<5; i++) {
  promises.push(someAsyncFn(i))
}

Promise.all(promises)

注意它在执行后如何产生正确的值

现在我们想要同样的东西,但应用到您的代码库:

function addProducts (products) {
  return new Promise(
    (resolve, reject) => {
      const promises = [];
      const ref = firebase.ref('products');
      const max = Math.ceil(products.length / MAXIMUM_POST_SIZE)

      for (let i = 0; i < max; i++) {
        const indexToStartExtraction = i * MAXIMUM_POST_SIZE;
        const sizeOfExtraction = indexToStartExtraction + MAXIMUM_POST_SIZE;
        const extractedProducts = products.slice(indexToStartExtraction, sizeOfExtraction);
        const normalizedPostData = arrayToJsonObjectWithUid({ products: extractedProducts, ref });
        promises.push(ref.update(normalizedPostData));
      }

      Promise.all(promises)
        .then(resolve)
        .catch(reject);
    },
  );
}

我做了一些清理,但想法是一样的,你可以试试这个代码,看看它是否产生了正确的结果?

【讨论】:

  • OP 的代码已经做到了,并行运行更新。显然他不想那样做。
  • 那么我们需要知道他遇到了什么错误。通读他的代码,看起来不错。只是说做他已经在做的事情并没有那么有帮助。
猜你喜欢
  • 2017-12-16
  • 2015-11-13
  • 2016-04-13
  • 2020-08-16
  • 2022-12-08
  • 2017-11-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-13
相关资源
最近更新 更多