有没有一种方法可以干净地创建一个在鼠标左键向上或向下后解析的承诺?

Is there a way to cleanly create a promise that resolves after left mouse up or down?

在我的客户端代码中,我有

const whatMouseDid = await mouseDoesSomething()

后一个函数看起来像:

async mouseDoesSomething() {
    const mouseUp = (resolve) => {
        const handler = (evt) => {
            if (evt.button === 0)
                resolve("up")
        }
        return handler
    }
    const mouseDown = (resolve) => {
        const handler = (evt) => {
            if (evt.button === 0)
                resolve("down")
        }
        return handler
    }
    return new Promise((resolve, reject) => {
        document.addEventListener('mouseup', mouseUp(resolve))
        document.addEventListener('mousedown', mouseDown(resolve))
    })
}

这已经比我希望的要复杂一些,但仍然易于管理。但是,有一个问题——永远不会删除侦听器。而且因为我需要传递对 Promise.resolve 的引用,所以我不能轻易地 removeEventListener。我能想到的唯一方法是保留一个可变的处理程序列表以及它们分配给的事件、目标等,并在处理程序函数中迭代该列表并删除附加的处理程序。可选参数 {once: true} 也不起作用,因为如果单击的按钮不是我想要的,我不会解析。

这一切都让人感觉非常复杂,让我觉得我只是错过了一个显而易见的简单方法;我是吗还是真的有这么麻烦?

And because I need to pass in a reference to Promise.resolve, I can't removeEventListener easily. The only way I can think to do this is to keep a mutable list

为什么这么复杂?您在 promise 执行器中创建函数,只需引用它们即可。

mouseDoesSomething() {
    return new Promise((resolve, reject) => {
        const mouseUphandler = evt => {
            if (evt.button === 0) {
                resolve("up")
                document.removeEventListener('mouseup', mouseUpHandler)
                document.removeEventListener('mousedown', mouseDownHandler)
            }
        }
        const mouseDownhandler = evt => {
            if (evt.button === 0) {
                resolve("down")
                document.removeEventListener('mouseup', mouseUpHandler)
                document.removeEventListener('mousedown', mouseDownHandler)
            }
        }
        document.addEventListener('mouseup', mouseUpHandler)
        document.addEventListener('mousedown', mouseDownHandler)
    })
}

当然有很多代码重复,但是您可以通过动态创建处理程序来再次抽象它 - 您只需要在声明两个处理程序的范围内进行:

mouseDoesSomething() {
    return new Promise((resolve, reject) => {
        const makeHandler = dir => evt => {
            if (evt.button === 0) {
                resolve(dir)
                document.removeEventListener('mouseup', mouseUpHandler)
                document.removeEventListener('mousedown', mouseDownHandler)
            }
        }
        const mouseUphandler = makeHandler("up")
        const mouseDownhandler = makeHandler("down")
        document.addEventListener('mouseup', mouseUpHandler)
        document.addEventListener('mousedown', mouseDownHandler)
    })
}

您可以创建一个辅助函数来在解析函数时调用:

mouseDoesSomething() {
  return new Promise((resolve, reject) => {
    const mouseUp = (evt) => {
      if (evt.button === 0) resolveAndRemove("up");
    };
    const mouseDown = (evt) => {
      if (evt.button === 0) resolveAndRemove("down");
    };

    function resolveAndRemove(val) {
      document.removeEventListener("mouseup", mouseUp);
      document.removeEventListener("mousedown", mouseDown);
      resolve(val);
    }

    document.addEventListener("mouseup", mouseUp);
    document.addEventListener("mousedown", mouseDown);
  });
}

此外,由于您没有使用 await,因此无需将函数标记为 async

以前的答案很好,(关闭)未来只需 2 美分的答案:

EventTarget.addEventListener 现在 accepts an AbortSignal 您可以在需要时使用它来删除事件侦听器。

所以在 Firefox Nightly (86) 和 Chrome Canary(90) 中你可以做

async function mouseDoesSomething() {
  return new Promise( (resolve, reject) => {
    let count = 0;
    const controller = new AbortController();
    const onevent = (evt) => {
      console.log( ++count );
      if( count >= 5 ) {
        resolve();
        controller.abort();
      }
    };
    document.addEventListener( 'mouseup', onevent, { signal: controller.signal } );
    document.addEventListener( 'mousedown', onevent, { signal: controller.signal } );
  } );
}
mouseDoesSomething().then( () => console.log('done') );

要检测支持,您可以使用通常的 属性-bag 陷阱:

const support = (() => {
  let support = false;
  const trap = {};
  Object.defineProperty(trap, "signal", { get() { support = true; } } );
  new EventTarget().addEventListener('foo', ()=>{}, trap);
  return support;
})();

console.log( support );