【问题标题】:Conditionally chaining promises inside and outside of a loop in javascript在javascript中的循环内部和外部有条件地链接承诺
【发布时间】:2025-12-28 04:55:11
【问题描述】:

在下面的代码中,当循环内的条件满足时,我们无法确定 asyncFunction 的调用顺序。

siblings.forEach(function(sibling, index) {
  // This condition may only be satisfied once (only one sibling may have data-selected true)
  if (sibling.getAttribute('data-selected') == 'true') { 
        asyncFunction(sibling);     
  }
})

asyncFunction(element);

因为我知道 asyncFunction() 返回一个承诺,我可以将它们链接起来以确保顺序:

asyncFunction(sibling).then(asyncFunction(element));

但是如何在考虑条件的情况下做到这一点?

我考虑过将循环包装在一个 Promise 中,当条件满足或循环结束后解决,但这似乎有点令人费解。

// Untested
function checkSiblings(siblings) {
    let promise = new Promise(function(resolve, reject) {
        siblings.forEach(function(sibling, index) {
          if (sibling.getAttribute('data-selected') == 'true') { 
                resolve(asyncFunction(sibling));    
          }
        })
        resolve();
    })
}

checkSiblings(siblings).then(asyncFunction(element));

我确信这个问题之前已经解决过,但我找不到合适的搜索键盘。

谢谢

【问题讨论】:

  • 循环与异步调用没有任何关系,因此在涉及承诺之前完成循环。此外,.forEach() 现在应该尽量避免使用,因为它的循环控制比普通的for 循环要少得多,尤其是当您在找到所需内容时想要中止循环时。
  • 对不起@jfriend00,但我不完全理解“在涉及承诺之前完成循环”的意思,你能举个例子吗?谢谢
  • 嗯,你在循环中调用resolve(),它只会在你第一次调用它时对承诺做任何事情。因此,您显然是在循环中的第一个匹配项上解决了一个承诺,然后继续运行循环的其余部分。因此,当循环完成时,promise 没有关联。我不知道您是否不需要循环继续。如果你这样做了,那么当循环完成时,promise 就没有正确连接。如果您不需要循环继续,那么您正在运行一堆不必要的代码。无论哪种方式,这都不是一个理想的设计。

标签: javascript loops promise control-flow


【解决方案1】:

如果只有一个兄弟姐妹可以满足条件,使用find 将简化您的代码:

const selected = [...siblings].find( elem => elem.getAttribute('data-selected') == 'true' );

checkSiblings 标记为async,这样它总是返回一个Promise,并让它在适当的时候返回asyncFunction Promise:

return selected ? asyncFunction(selected) : null;

然后你可以像以前一样链接它们:

checkSiblings(siblings).then(() => asyncFunction(element));

你会得到这样的东西:

async function checkSiblings (siblings) {
  const selected = [...siblings].find( elem => elem.getAttribute('data-selected') == 'true' );
  return selected ? asyncFunction(selected) : null
}

checkSiblings(siblings).then(() => asyncFunction(element));

【讨论】:

  • 谢谢。如果我错了,请纠正我,我认为 find() 不能用于节点列表(Siblings 是 html 元素的节点列表,抱歉我没有指定)或者可以吗?
  • 使用 Array.from(siblings).find(...) 或使用扩展运算符 [...siblings].find(...)
  • 哦。正确的。对不起。我会按照@emi 的正确建议用点差来更新答案。
  • 可能是 find 在这种情况下不起作用?我做了一个 jsFiddle 来测试它:jsfiddle.net/plagasul/auLbrd9j/7
  • 您正在调用document.querySelector('li'),它返回a single element or null。如果您只获得一个元素,则无需将其转换为数组或调用 find 。或许您打算改为致电querySelectorAll
【解决方案2】:

有几种方法。其中之一是使用Array.reduceresolve promises sequentially

[...siblings].reduce(
  async (prev, sibling)) => {

    // Await initial/previous promise (discard results)
    await prev;

    // This condition may only be satisfied once
    // (only one sibling may have data-selected true)
    if (sibling.getAttribute('data-selected') == 'true') { 
      // Return the async function promise
      return asyncFunction(sibling);
    }

    // async functions always returns a promise.
    // No need to return anything

   // Initial promise autoresolved
}, Promise.resolve())

// Once all have been executed
.then( () => asyncFunction(element));

【讨论】: