Await dispatchEvent: 如何在监听器是异步的情况下同步地监听dispatchEvent

3
为了开发软件插件,我需要监听使用dispatchEvent生成的事件,并在接收到此事件时执行一些操作。重要的是,在软件继续执行dispatchEvent后的代码之前,我的操作应该完成。
因为dispatchEvent是同步的,所以理论上应该是这样...不幸的是,我的操作是异步的(基本上我需要等待视频被搜索),在这种情况下,dispatchEvent会在我的代码结束之前继续执行。当调用的函数是异步的时候,是否有可能也获得dispatchEvent的同步行为? 示例: 我希望以下代码能够产生输出结果:
Putting the clothes in the washing machine.
Machine started...
Machine ended...
Picking the washed clothes.

不幸的是,衣服在洗衣机结束之前被拿走了:

Putting the clothes in the washing machine.
Machine started...
Picking the washed clothes.
Machine ended...

我使用了这段代码:

var button = document.getElementById('button');
button.addEventListener('click', event => {
  console.log("Putting the clothes in the washing machine.");
  const machineResult = button.dispatchEvent(new CustomEvent('startmachine', {
    'detail': {
      timeProgram: 3000
    }
  }));
  if (machineResult.defaultPrevented) {
    console.log("Picking the dirty clothes.");
  } else {
    console.log("Picking the washed clothes.");
  }
});
button.addEventListener('startmachine', async event => {
  console.log("Machine started...");
  await new Promise(r => setTimeout(r, event.detail.timeProgram));
  console.log("Machine ended...");
});
<button id="button">Wash my clothes</button>

编辑:我发现了一个不太好的解决方案

由于看起来似乎不可能,我采用了另一种方法,不幸的是,它需要在代码dispatchEvent后进行一些更改(由于我不负责代码的这部分,我更喜欢一种在dispatchEvent之后不更改代码的方法)。

这个想法是在事件中添加一个数组,每个侦听器都可以放置一个异步函数。然后,在dispatchEvent完成后,代码会执行此数组中的所有函数,并且如果有任何一个函数返回false,则认为事件已被中止。

  var button = document.getElementById('button');
  button.addEventListener('click', async event => {
      console.log("Putting the clothes in the washing machine.");
      var listOfActions = [];
      const machineResult = button.dispatchEvent(new CustomEvent('startmachine', { 'detail': {timeProgram: 3000, listOfActions: listOfActions} }));
      results = await Promise.all(listOfActions.map(x => x()));
      if (machineResult.defaultPrevented || !results.every(x => x != false)) { // Do not use == true or boolean to ensure returning nothing does not abort.
          console.log("Picking the dirty clothes.");
      } else {
          console.log("Picking the washed clothes.");
      }
  });
  
  button.addEventListener('startmachine', event => {
      event.detail.listOfActions.push( async () => {
          console.log(">>> Machine started...");
          await new Promise(r => setTimeout(r, event.detail.timeProgram));
          console.log("<<< Machine ended...");
          // If you want to say that the clothes are dirty:
          // return false
      });
  });
<button id="button">Wash my clothes</button>

编辑2

使用Promise更好地处理错误:

        var button = document.getElementById('button');
  button.addEventListener('click', async event => {
      console.log("Putting the clothes in the washing machine.");
      var listOfActions = [];
      let cancelled = !button.dispatchEvent(new CustomEvent('startmachine', { 'detail': {timeProgram: 3000, listOfActions: listOfActions} }));
      results = await Promise.all(listOfActions);
      // Do not use == true or boolean to ensure returning nothing does not abort.
      cancelled = cancelled || !results.every(x => x != false)
      if (cancelled) {
          console.log("Picking the dirty clothes.");
      } else {
          console.log("Picking the washed clothes.");
      }
  });
  
  button.addEventListener('startmachine', event => {
      event.detail.listOfActions.push((async () => {
          console.log(">>> Machine started...");
          await new Promise(r => setTimeout(r, event.detail.timeProgram));
          console.log("<<< Machine ended...");
          // If you want to say that the clothes are dirty:
          //return false
      })());
      // ^-- The () is useful to push a promise and not an async function.
      // I could also have written let f = async () => { ... };  event....push(f())
  });
<button id="button">Wash my clothes</button>


2
你负责代码的两个部分吗?即发出事件和接收事件的部分?如果是这样,最好重构一下,不要使用事件,例如直接从“click”监听器调用“startmachine”处理程序,并从那里很好地处理返回的Promise。 - Kaiido
@Kalido 很遗憾,不行。我可以尝试向软件请求一个拉取请求,以便它更新其代码(以向后兼容的方式),但理想情况下,我更喜欢只在“startmachine”侦听器端更改代码的解决方案(我甚至不确定PR是否会被接受)。否则,我想我可以使用“全局变量数组”,让侦听器将承诺添加到该数组中,在dispatchEvent之后执行这些承诺。 - tobiasBora
1
如果你必须等待Promise解决才能知道是否应该防止事件发生,那么就没有运气了,发射器方始终会在你写入之前收到事件。如果你来自另一边,可能会有一些黑客技巧,但是在这里... - Kaiido
@Kaiido 好的,谢谢。我改进了我的问题,并加入了一个不太好的解决方案,其中包含了一些技巧,但我提到我不喜欢它,因为我需要在另一边。 - tobiasBora
1个回答

1
“dispatchEvent”函数在调用异步函数时是否可能实现同步行为?
不可能。
提议是在事件中添加一个数组,每个监听器都可以放置一个异步函数。然后,在“dispatchEvent”完成后,代码将执行此数组中的所有函数。
是的,这是一个好主意。但我建议将其作为Promise数组而不是函数数组。
Web平台API在“ExtendableEvent”中使用了相同的模式。不幸的是,该构造函数仅可在服务工作者内使用。

谢谢您的回答。您建议使用Promise是因为它可以更好地处理错误吗?我也尝试更新代码以使用Promise,但我找不到一种优雅的方式来使用await语法来定义我的函数:使用Promise,代码很快变得难以阅读。不幸的是,推送async (resolve, reject) => ...并不能产生与推送new Promise ...相同的结果。 - tobiasBora
是的,调用函数可能会导致同步异常 - 如果您只有一个承诺数组,请将其传递给 Promise.all(或 Promise.allSettled),然后完成它。我考虑声明一个 async function startMachine() { ... },然后在事件处理程序中添加 event.detail.waitFor.push(startMachine()) - Bergi
哦,将异步函数转换为 Promise 如此简单^^ 非常感谢,我已经更新了我的答案! - tobiasBora
此外,我的解决方案的问题在于发送 dispatchEvent 的函数现在需要是异步的... 而且它很可能会破坏我正在编写插件的软件的向后兼容性... 我想没有办法在不破坏兼容性的情况下解决我的问题了吧? - tobiasBora
不,同步等待异步操作是不可能的。 - Bergi
可能可以通过“有条件地异步”来使分派事件的函数向后兼容,即仅在detail.waitFor数组中有条目时返回一个Promise,否则对于不使用此功能的人保持同步,但这会打开潘多拉魔盒。不要这样做,这是一个棘手的问题 - 只需进行主要发布即可。 - Bergi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接