首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >等待给定事件的所有处理程序完成

等待给定事件的所有处理程序完成
EN

Stack Overflow用户
提问于 2022-06-11 21:19:15
回答 1查看 953关注 0票数 0

我正在编写Javascript库,我想知道是否可以这样做。

我想在元素上触发一个自定义事件,但我不知道订阅了哪个事件处理程序,也不知道订阅了多少事件。然后,我想等待所有这些事件处理程序完成,然后检查它们是否执行了给定的操作(例如,“拒绝”事件)。如果没有,则触发事件的函数将继续进行。

为了明确起见,我可以向事件处理程序提供参数,例如"() => reject()“函数,或者为事件处理程序定义任何类型的”契约“,但我不能修改订阅事件处理程序的代码。这样的代码将由图书馆的用户编写。

这有可能/可取吗?

谢谢!

更新

这里是我想要使用的代码片段的一个例子,考虑到库的最终用户实际上是自己调用addEventListener()或$.on()。

代码语言:javascript
复制
$body = $("body")
function rejectEvent(o) {
    o.reject();
}
function acceptEvent(o) {}
function triggerEvent() {
    let isRejected = false;
    $body.trigger('custom-event', {
        reject: () => isRejected = true;
    });
    // Wait for all event handlers to complete...
    if (isRejected) {
         console.log('stop');
    } else {
         console.log('proceed');
    }
}

triggerEvent(); // Should display 'proceed'
$body.on('custom-event', function(e, o) {
    console.log('do nothing');
});
triggerEvent(); // Should display 'do nothing' then 'proceed'
$body.on('custom-event', function(e, o) {
    console.log('reject');
    o.reject();
});
triggerEvent(); // Should display 'do nothing' then 'reject' then 'stop'
$body.off('custom-event');
$body.on('custom-event', async function(e, o) {
    setTimeout(() => {
        console.log('reject');
        o.reject();
    }, 5000);
});
triggerEvent(); // Should display 'proceed' then 'reject'

如本例所示,只要事件处理程序被同步执行,我就可以正确地检索事件处理程序的拒绝状态(至少我从googling本主题中理解了这一点)。但是,我遇到的主要问题是,最终用户是否将事件处理程序定义为异步的。

到目前为止,我看到的最佳选择是记录异步事件处理程序不受支持,但我也希望能够支持它们。

EN

回答 1

Stack Overflow用户

发布于 2022-06-12 15:26:24

从上面的评论..。

"@PeterSeliger更改addEventListener方法听起来很有趣。我想这样做是有意义的,并尝试将最终用户事件处理程序(可能是异步的)封装在同步函数中,并确保用户事件处理程序在包装器函数终止之前完成。将给它一些想法,但我欢迎更多的建议。“

半句话..。“同步函数中的最终用户事件处理程序(可能是异步的)”.我想知道是否应该将经典(fire和忘记)事件处理与许诺和/或异步等待语法相结合。实际上,到目前为止,我从未遇到过任何承诺的/异步事件处理程序。如果将后者(异步事件处理程序函数)与分派自定义事件混为一谈,则必须用基于异步函数的实现替换原型dispatchEvent,或者必须为其提供自己的原型额外方法。- Peter Seliger

当然这是可以做到的。

首先是HTMLElement.prototype.addEventListener的拦截方法,在最初的实现中封装额外的功能。因此,稍后还可以通过用自己的实现替换HTMLElement.prototype.dispatchEvent来控制自定义事件的分派,或者想出一个自定义的HTMLElement.prototype.dispatchMonitoredEvent方法,或者更好的是,如果一个人完全控制了调度环境,就可以将它实现为lib-author可访问的dispatchMonitoredEvent方法。

至于拦截方法。这样做是为了将特定于元素的listener存储在(甚至可能是全局可访问的) listenerStorage中,这是一个基于节点引用的WeakMap实例,它保存每个节点的侦听器E 218 实例。后者具有特定于事件类型的条目,其中可以通过一个事件类型的键访问处理程序数组值。

代码语言:javascript
复制
function handleFormControlEvent({ type: eventType, currentTarget: node }) {
  console.log({
    node,
    nodeValue: node.value,
    eventType,
  });
}
function logFormControlHandlerCount({ type: eventType, currentTarget: node }) {
  console.log({
    [ `${ eventType }HandlerCount` ]: listenerStorage
      .get(node)
      .get(eventType)
      .length
  });
}
function logCheckboxState({ currentTarget: { checked } }) {
  console.log({ checked });
}

document
  .querySelectorAll('input')
  .forEach(elmNode => {
    elmNode.addEventListener('input', handleFormControlEvent);
    elmNode.addEventListener('input', logFormControlHandlerCount);
  });
document
  .querySelectorAll('[type="checkbox"]')
  .forEach(elmNode => {
    elmNode.addEventListener('click', handleFormControlEvent);
    elmNode.addEventListener('click', logCheckboxState);
    elmNode.addEventListener('click', logFormControlHandlerCount);
  });

document
  .querySelector('[type="checkbox"]')
  .dispatchEvent(new CustomEvent('click'));
代码语言:javascript
复制
body { margin: 0; }
fieldset { width: 40%; margin: 0; padding: 0 0 4px 4px; }
.as-console-wrapper { left: auto!important; min-height: 100%!important; width: 58%; }
代码语言:javascript
复制
<fieldset>
  <legend>test area</legend>
  <label>
    <span>Foo:</span>
    <input type="text" value="Foo" />
  </label>
  <label>
    <span>Bar:</span>
    <input type="checkbox" />
  </label>
</fieldset>

<script>
HTMLElement.prototype.addEventListener = (function (proceed) {
  window.listenerStorage = storage = new WeakMap;

  return function addEventListener(type, handler) {
    const currentTarget = this;

    let listeners = storage.get(currentTarget);
    if (!listeners) {
      listeners = new Map;
      storage.set(currentTarget, listeners);
    }
    let handlerList = listeners.get(type);
    if (!handlerList) {
      handlerList = [];
      listeners.set(type, handlerList);
    }
    if (!handlerList.includes(handler)) {
      handlerList.push(handler)
    }
    // proceed with delegation to the original implementation.
    proceed.call(currentTarget, type, handler);
  };
}(HTMLElement.prototype.addEventListener));
</script>

监视依赖于两种处理程序函数,它们必须返回一个值(这不是传统的处理程序函数方法),从中可以判断失败/成功,或者依赖于已解析/解决的异步处理程序函数的返回值(这甚至更不寻常)。在上述示例代码的基础上,非/原型异步dispatchMonitoredEvent函数/方法的实现和使用如下所示.

代码语言:javascript
复制
function handleFormControlEvent({ type: eventType, currentTarget: node }) {
  console.log({
    node,
    nodeValue: node.value,
    eventType,
  });
  // function statement as event handler, but with unusual return value.
  return { controlEventSuccess: true };
}
function logFormControlHandlerCount({ type: eventType, currentTarget: node }) {
  console.log({
    [ `${ eventType }HandlerCount` ]: listenerStorage
      .get(node)
      .get(eventType)
      .length
  });
  // function statement as event handler, but with unusual return value.
  return { handlerCountSuccess: true };
}
async function logCheckboxState({ currentTarget: { checked } }) {
  // asynchronous function as event handler.
  return new Promise(resolve =>
    setTimeout(() => {

      console.log({ checked });
      resolve({ checkboxStateSuccess: true });

    }, 2000)
  );
}

document
  .querySelectorAll('input')
  .forEach(elmNode => {
    elmNode.addEventListener('input', handleFormControlEvent);
    elmNode.addEventListener('input', logFormControlHandlerCount);
  });
document
  .querySelectorAll('[type="checkbox"]')
  .forEach(elmNode => {
    elmNode.addEventListener('click', handleFormControlEvent);
    elmNode.addEventListener('click', logCheckboxState);
    elmNode.addEventListener('click', logFormControlHandlerCount);
  });

document
  .querySelector('[type="checkbox"]')
  .dispatchEvent(new CustomEvent('click'));

console.log(
  '\n+++ dispatched monitored custom events +++\n\n'
);

(async function () {

  console.log(
    '... trigger ... execute all at once and log at the end ...'
  );
  // execute all at once ...
  Promise
    .all([
      document
        .querySelector('[type="checkbox"]')
        .dispatchMonitoredEvent(new CustomEvent('click')),
      document
        .querySelector('input')
        .dispatchMonitoredEvent(new CustomEvent('input')),
    ])
    // ... and log at the end.
    .then(values =>
      values.forEach(returnValues =>

        console.log({ returnValues })
      )
    );

  console.log(
    '... trigger ... execute and log one after the other ...'
  );
  // execute and log one after the other.
  let returnValues = await document
    .querySelector('[type="checkbox"]')
    .dispatchMonitoredEvent(new CustomEvent('click'));

  console.log({ returnValues });

  returnValues = await document
    .querySelector('input')
    .dispatchMonitoredEvent(new CustomEvent('input'));

  console.log({ returnValues });

})();
代码语言:javascript
复制
body { margin: 0; }
fieldset { width: 40%; margin: 0; padding: 0 0 4px 4px; }
.as-console-wrapper { left: auto!important; min-height: 100%!important; width: 58%; }
代码语言:javascript
复制
<fieldset>
  <legend>test area</legend>
  <label>
    <span>Foo:</span>
    <input type="text" value="Foo" />
  </label>
  <label>
    <span>Bar:</span>
    <input type="checkbox" />
  </label>
</fieldset>

<script>
HTMLElement.prototype.addEventListener = (function (proceed) {
  window.listenerStorage = storage = new WeakMap;

  return function addEventListener(type, handler) {
    const currentTarget = this;

    let listeners = storage.get(currentTarget);
    if (!listeners) {
      listeners = new Map;
      storage.set(currentTarget, listeners);
    }
    let handlerList = listeners.get(type);
    if (!handlerList) {
      handlerList = [];
      listeners.set(type, handlerList);
    }
    if (!handlerList.includes(handler)) {
      handlerList.push(handler)
    }
    // proceed with delegation to the original implementation.
    proceed.call(currentTarget, type, handler);
  };
}(HTMLElement.prototype.addEventListener));

async function dispatchMonitoredEvent({
  isTrusted =false, bubbles = false,
  cancelBubble = false, cancelable = false,
  composed = false, defaultPrevented = false,
  detail = null, eventPhase = 0, path = [],
  returnValue = true, timeStamp = Data.now(),
  type = null,
}) {
  // @TODO ... custom event data handling/copying is in need of improvement.
  const currentTarget = this;
  const monitoredEvent = {
    isTrusted, bubbles, cancelBubble, cancelable, composed, currentTarget,
    defaultPrevented, detail, eventPhase, path, returnValue,
    srcElement: currentTarget, target: currentTarget,
    timeStamp, type,
  };
  const handlerList = listenerStorage
    .get(currentTarget)
    ?.get(type) ?? [];

  return (await Promise
    .all(
      handlerList
        .map(handler =>
          handler(monitoredEvent)
          // (async (evt) => handler(evt))(monitoredEvent)
        )
    ));
};
HTMLElement
  .prototype
  .dispatchMonitoredEvent = dispatchMonitoredEvent;
</script>

编辑

在学习了上述方法和实现之后,并考虑到OP稍后提供的示例代码,可以为自定义可取消事件提供一个实现,其中整个async...await处理都是围绕自定义事件的增强detail属性构建的。

因此,无论是否注册了正常/经典或异步处理程序函数,都不会更改处理程序的属性(函数的预期/要处理参数的数量),而是通过将例如唯一的事件-参数的detail.proceed属性设置为false来控制调度过程的取消。

元素的所有已注册事件类型特定处理程序函数都将由异步生成器处理,异步生成器控制函数的执行,并有权访问当前事件对象的引用。根据失败/拒绝的处理程序函数或当前的event.detail.proceed值,异步生成器将继续/停止屈服。

运行生成器的异步dispatchCustomCancelableEvent方法确实返回一个对象,该对象承载所有有关已确定的调度过程的数据,如successcancelederroreventevent.detail还提供了有关所涉及的handlerscancelHandler (负责任何类型的取消的处理程序)的附加信息。

代码语言:javascript
复制
async function triggerTestEvent(elmNode) {
  const result = await elmNode
    .dispatchCustomCancelableEvent('custom-cancelable-event');

  const { /*event, success, */canceled/*, error = null*/ } = result;

  // in order to meet the OP's requirement of ...
  // ... "Wait for all event handlers to complete"
  if (canceled) {
    console.log('stop', { result });
  } else {
    console.log('proceed', { result });
  }
}

(async () => {
  const testNode = document.querySelector('div');

  // should display 'proceed'.
  await triggerTestEvent(testNode); 

  testNode
    .addEventListener('custom-cancelable-event', () =>
      console.log('do nothing')
    );
  // should display 'do nothing' then 'proceed'.
  await triggerTestEvent(testNode);

  testNode
    .addEventListener('custom-cancelable-event', evt => {
      console.log('reject');
      evt.detail.proceed = false; // was ... o.reject();
    });
  testNode
    .addEventListener('custom-cancelable-event', () =>
      console.log('+++ should never be displayed +++')
    );
  // should display 'do nothing' then 'reject' then 'stop'
  await triggerTestEvent(testNode);


  testNode
    .removeCustomListeners('custom-cancelable-event');

  testNode
    .addEventListener('custom-cancelable-event', (/*evt*/) =>
      console.log('... handle event ...')
    );
  testNode
    .addEventListener('custom-cancelable-event', async (evt) =>
      new Promise((resolve/*, reject*/) =>
        setTimeout(() => {

          console.log('... cancle event ...');
          // reject('cancle event');

          evt.detail.proceed = false; // was ... o.reject();
          resolve();

        }, 5000)
      )
    );
  testNode
    .addEventListener('custom-cancelable-event', () =>
      console.log('+++ should never be displayed +++')
    );
  // should display
  // '... handle event ...' then '... cancle event ...' then 'stop'
  await triggerTestEvent(testNode); 

})();
代码语言:javascript
复制
body { margin: 0; }
.as-console-wrapper { left: auto!important; min-height: 100%!important; width: 80%; }
代码语言:javascript
复制
<div>dispatch test node</div>

<script>
HTMLElement.prototype.addEventListener = (function (proceed) {
  window.listenerStorage = storage = new WeakMap;

  return function addEventListener(type, handler) {
    const currentTarget = this;

    let listeners = storage.get(currentTarget);
    if (!listeners) {
      listeners = new Map;
      storage.set(currentTarget, listeners);
    }
    let handlerList = listeners.get(type);
    if (!handlerList) {
      handlerList = [];
      listeners.set(type, handlerList);
    }
    if (!handlerList.includes(handler)) {
      handlerList.push(handler)
    }
    // proceed with delegation to the original implementation.
    proceed.call(currentTarget, type, handler);
  };
}(HTMLElement.prototype.addEventListener));

HTMLElement.prototype.removeCustomListeners =
  function removeCustomListeners (type) {
    storage
      .get(this)
      ?.delete?.(type);
  };

HTMLElement.prototype.dispatchCustomCancelableEvent = (function () {
  async function* createCancelableDispatchablesPool(handlerList, evt) {
    handlerList = [...handlerList];

    let isProceed = true;
    let handler;
    let recentHandler;

    while (isProceed && (handler = handlerList.shift())) {
      try {
        if (evt.detail.proceed === true) {
          recentHandler = handler;

          yield (await handler(evt));
        } else {
          evt.detail.proceed = isProceed = false;
          evt.detail.cancelHandler = recentHandler;
        }
      } catch (reason) {
        // an (async) handler function's execution
        // could also just fail with or without reason.
        evt.detail.proceed = isProceed = false;
        evt.detail.cancelHandler = recentHandler;

        yield (
          new Error(String(reason ?? 'failed without reason'))
        );
      }
    }
  }
  return async function dispatchCustomCancelableEvent(type, options = {}) {
    const currentTarget = this;

    const handlerList = listenerStorage
      .get(currentTarget)
      ?.get(type) ?? [];

    Object.assign((options.detail ??= {}), {

      currentTarget,
      target: currentTarget,

      // extend a custom event's `detail`
      // by a boolean `proceed` property.
      // and a list of all event `handlers`.
      proceed: true,
      handlers: handlerList,
    });
    const customEvent = new CustomEvent(type, options);

    const dispatchablesPool =
      createCancelableDispatchablesPool(handlerList, customEvent);

    const dispatchResult = {
      success: true,
      canceled: true,
    };
    for await (const result of dispatchablesPool) {
      // an (async) handler function's execution
      // could also just fail with or without reason.
      if (result instanceof Error) {
        dispatchResult.success = false;
        dispatchResult.error = result;
      }
    }
    if (customEvent.detail.proceed === true) {

      dispatchResult.canceled = false;

    } else if (!customEvent.detail.cancelHandler) {

      customEvent.detail.cancelHandler =
        // customEvent.detail.handlers.at(-1)
        customEvent.detail.handlers.slice(-1)[0];
    }
    return Object.assign(dispatchResult, { event: customEvent });
  };
}());
</script>

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72587904

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档