简体   繁体   中英

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

In my client code, I have

const whatMouseDid = await mouseDoesSomething()

with the latter function looking like:

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))
    })
}

Which is already a bit more convoluted than I'd prefer, but still manageable. However, there's a problem - the listeners are never removed. 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 of handlers and the events, targets, etc they are assigned to, and in the handler function(s), iterate over that list and remove attached handlers. The optional param {once: true} also won't work because I don't resolve if the button clicked is not the one I want.

This all feels super convoluted, and makes me think I'm just missing the obvious easy way to do this; am I? Or is it really this much of nuisance?

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

Why so complicated? You create functions in the promise executor, just reference them.

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)
    })
}

Surely that's lots of code duplication, but you can abstract this again by creating the handlers dynamically - you just need to do it in the scope where the two handlers are declared:

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)
    })
}

You could create a helper function to call when you resolve your function:

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);
  });
}

Also, since you're not using await , you don't need to mark the function as async .

Previous answers are fine, just a 2 cents answer for the (close) future:

EventTarget.addEventListener now accepts an AbortSignal which you can use to remove the event listener when needed.

So in Firefox Nightly (86) and in Chrome Canary(90) you can do

 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') );

To detect for support you can use the usual property-bag trap:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM