You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现视频事件监听器仅触发一次,移除重复调用的Promise回调

问题描述

我用以下代码创建WebGL2纹理,现在遇到个问题:playing事件的回调会在视频每次开始时重复触发,timeupdate事件则会持续触发。虽然没严重问题,但我需要它们仅触发一次,有没有办法在第一次调用后移除这些监听器?

问题代码

async function loadVideo (src, crossOrigin)
{
   let video  = makeVideo (src, crossOrigin);

   return new Promise((resolve, reject) =>
   {
       Promise.allSettled ([
            new Promise ( (resolve, reject) => {video.addEventListener('playing',     () => {resolve(1);}, true )}   ),
            new Promise ( (resolve, reject) => {video.addEventListener('timeupdate',  () => {resolve(1);}, true )}   )   ])
            .then((values) => 
       {
         resolve(video);
       });
   });
}

完整代码示例

function makeVideo (src, crossOrigin)
{
   let video  = document.createElement("video");
   if (crossOrigin) video.crossOrigin  =  crossOrigin;
   video.src         =  src;
   video.autoplay    = true;
   video.playsInline = true;
   video.muted       = true;
   video.loop        = true;
   video.play();
   return video;
}

async function loadVideo (src, crossOrigin)
{
   let video  = makeVideo (src, crossOrigin);
   video.width = 300;
   return new Promise((resolve, reject) =>
   {
       Promise.all ([
            new Promise ( (resolve, reject) => {video.addEventListener('playing',     () => {resolve(1);}, true )}   ),
            new Promise ( (resolve, reject) => {video.addEventListener('timeupdate',  () => {resolve(1);}, true )}   )   ])
            .then((values) => 
       {
         resolve(video);
       });
   });
}
document.addEventListener('DOMContentLoaded', async () => 
{
   loadVideo ("https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4")
      .then(vd => {document.body.appendChild(vd)});
});
解决方案

有两种简单方式实现监听器仅触发一次:

方法1:使用once配置项

addEventListener原生支持once参数,设置为true后,监听器会在触发一次后自动移除,无需手动处理。修改后的代码如下:

async function loadVideo (src, crossOrigin)
{
   let video  = makeVideo (src, crossOrigin);

   return new Promise((resolve, reject) =>
   {
       Promise.allSettled ([
            new Promise ( (resolve) => {
                video.addEventListener('playing', () => resolve(1), { once: true });
            }),
            new Promise ( (resolve) => {
                video.addEventListener('timeupdate', () => resolve(1), { once: true });
            })
        ]).then(() => resolve(video));
   });
}

方法2:手动移除监听器

如果需要更灵活的控制逻辑,可以先把回调函数存为变量,触发后调用removeEventListener手动移除:

async function loadVideo (src, crossOrigin)
{
   let video  = makeVideo (src, crossOrigin);

   return new Promise((resolve, reject) =>
   {
       const handlePlaying = () => {
           video.removeEventListener('playing', handlePlaying, true);
           resolve(1);
       };
       const handleTimeUpdate = () => {
           video.removeEventListener('timeupdate', handleTimeUpdate, true);
           resolve(1);
       };

       Promise.allSettled ([
            new Promise ( (resolve) => {
                video.addEventListener('playing', handlePlaying, true);
            }),
            new Promise ( (resolve) => {
                video.addEventListener('timeupdate', handleTimeUpdate, true);
            })
        ]).then(() => resolve(video));
   });
}

补充说明

你的代码中使用了Promise.all/Promise.allSettled,意味着只有两个事件都触发一次后,才会resolve最终的video Promise。如果需求是只要其中一个事件触发就完成,可替换为Promise.race。


内容的提问来源于stack exchange,提问作者armagedescu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 14:52:43