是否可以在 jQuery.each() 中使用同步承诺睡眠?

Is it possible to use a sync promise sleep inside of jQuery.each()?

所以基本上。我想在同步函数中使用 jQuery 的每个函数。 不过,据我所知。它不起作用,因为每个函数只检查 return 值是否为 false 以中断循环。它不检查它是否是一个承诺,然后将其设置为等待它被解决。有没有一种简单的方法可以使 jQuery 的每个函数与 promises 同步?

我目前有一个替代方案,即创建一个循环迭代元素的 for 循环,但是写起来有点冗长,所以我想尽可能避免这种情况。

这是其中的一个 fiddle: https://jsfiddle.net/3pcvqswn/2/

function sleep(ms)
{
    return new Promise(resolve => setTimeout(resolve, ms));
}

$(document).ready(function()
{
    (async () =>
  {
    console.log("This method works, but its lengthy to write");
    var elements = $("div");

    for(var index = 0; index < elements.length; index++)
    {
        var el = $(elements.eq(index));

        console.log("The content is: " + el.text());

      await sleep(1000);
        }

    console.log("This method doesn't work, but its easier to write");
    $("div").each(async (index, el) =>
    {
        console.log("The content is: " + $(el).text());

      // doesn't wait
      await sleep(1000);
    });
  })();
});

我决定使用 for of,但同时拥有索引和元素回调的更准确的解决方案是

  Object.entries($("div")).forEach(async ([index, el]) =>
  {
    console.log("row ", index, el);
  });

否 - jQuery 的 .eachforEach 的工作方式类似。每个回调将一个接一个地触发,如果回调 returns 是一个 Promise(例如如果它是一个异步函数),则不会等待它 - 它的工作方式类似于

callback();
callback();
callback();

除非回调中的内容 blocks(如果您使用的是适当的编程实践,则不应如此),回调中的任何异步初始化都不会导致进一步的回调等待要完成的异步操作。

但是 jQuery 对象是可迭代的,所以使用简洁的 for..of 来迭代元素是微不足道的:

const sleep = ms => new Promise(res => setTimeout(res, ms));

(async () => {
  for (const elm of $("div")) {
    console.log("The content is: " + $(elm).text());
    await sleep(1000);
  }
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>text 1</div>
<div>text 2</div>
<div>text 3</div>

在我看来一点也不冗长。

为了在循环中使用 await,循环必须是 for 循环(for..in、for..of 或 for(let i = . ..),或者需要构建循环的迭代机制,以便等待最后一个回调解决,例如 reduce:

const sleep = ms => new Promise(res => setTimeout(res, ms));

[...$("div")].reduce(async (lastProm, elm) => {
  await lastProm;
  console.log("The content is: " + $(elm).text());
  await sleep(1000);
}, Promise.resolve());
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>text 1</div>
<div>text 2</div>
<div>text 3</div>


旁注几乎不值得一提 - 我之前说过在 .each 中使用 await 是行不通的

Unless something in the callback blocks (which it shouldn't if you're using decent programming practices)

如果您编写了一个昂贵且愚蠢的 sleep 阻塞版本,它会在到达下一秒之前消耗所有资源,那么 "possible" 在循环中等待,但它永远不应该完成:

const sleep = ms => {
  const now = Date.now();
  while (Date.now() - now < ms) {}
}

正确使用 Promises 会更好。

你不能强制 $.each 到 运行 连续(一个接一个)。它 运行 是每个元素 "simultaneously" 的函数。一种选择是编写您自己的 each 函数,该函数只执行您在问题中编写的较长版本:

const serialEach = async (f, elements) => {
   for(let index = 0; index < elements.length; index++) {
       const el = $(elements.eq(index))

       await f(el)
    }
}

然后你可以运行这样:

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms))
}

$(document).ready(() => {
    const elements = $("div")

    serialEach(async (el) => {
        console.log("The content is: " + el.text())

        await sleep(1000)
    }, elements)
})

请注意 serialEach return 是一个承诺,因此它会立即 return 并且您在 serialEach 调用之后的任何代码都将执行,即使 [=14] =] 正在遍历您的元素。如果您想在继续之前等待该迭代完成,您可以 await serialEach(并使 $.ready 回调 async)。

我不建议为此使用 .each。无论函数是否异步,该函数都会被执行。但是通过编程,您可以发挥创造力。

//code

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

let promise = Promise.resolve();
$("div").each((index, el) => {
  promise = promise.then(async () => {
    console.log("The content is: " + $(el).text());

    await sleep(1000);
  });
});

await promise;

//code