Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async support #122

Closed
hardcodet opened this issue Dec 3, 2020 · 7 comments
Closed

Async support #122

hardcodet opened this issue Dec 3, 2020 · 7 comments
Labels
can't Out of scope / too large / too seldom-used

Comments

@hardcodet
Copy link

hardcodet commented Dec 3, 2020

Hi there!

First of all: Thanks for this little gem! It's been tirelessly sending around messages for me for years now without any complaints :)

In noticed that pretty much all my message listeners are actually containing async code and return Promises, which will cause them to run in parallel and potentially also cause unhandled promise exceptions in case anything goes wrong. Since I wanted neither of those, I simply created an overload to mitt's emit:

emitAndAwait simply awaits every handler that returns a Promise, and then continues with the next one, thus ensuring sequential execution. Also, if a Promise failed (which it really shouldn't for a pub/sub subscriber), this would propagate properly to the caller.

        async emitAndAwait<T = any>(type: EventType, evt: T) : Promise<void> {
            const handlers = ((all.get(type) || []) as EventHandlerList).slice();
            const wildCardHandlers = ((all.get('*') || []) as WildCardEventHandlerList).slice()

            for (const handler of handlers) {
                // don't do Promise.all, but handle events one by one
                // if it's not actually a promise, await will just immediately continue
                await handler(evt);
            }

            for (const handler of wildCardHandlers) {
                await handler(type, evt);
            }
        }

Since this is returns a Promise too, it can be simply awaited by the caller:

await this.emitter.emitAndAwait("foo", fooMessage);

This has become my go-to replacement for all my mitt calls. Would that maybe make sense in the library itself?

@Lilja
Copy link

Lilja commented Jun 22, 2021

Hey! I found this code searching in the repository. How do you integrate it with mitt?

@hardcodet
Copy link
Author

Hi there

I would love to see this as part of the library (FYI @developit), but for now, I just copied to source code of mitt into my project, and added the method above.

@developit
Copy link
Owner

developit commented Jun 23, 2021

Asynchrony is expensive when unused, so this couldn't feasibly be landed in Mitt itself. There also isn't an obvious solution for how data should flow between handlers - something being async sortof implies it should have a return value, otherwise "async emit" is really just "sequential emit" or a queue.

However you can implement it as an add-on. I'd recommend calling it emitAsync() or something, rather than overwriting the normal emit() method:

import mitt from 'mitt';

export function mittAsync(all) {
  const inst = mitt(all);
  inst.emitAsync = emitAsync;
  return inst;
}

async function emitAsync(type, e) {
  let handlers = this.all.get(type);
  if (handlers) for (const f of handlers) await f(e);
  let handlers = this.all.get('*');
  if (handlers) for (const f of handlers) await f(type, e);
}

// usage:
const events = mittWithAsync();
events.on('get', async url => fetch(url).then(r=>r.json()));
await events.emit('get', '/foo.json');

@developit developit added the can't Out of scope / too large / too seldom-used label Jun 23, 2021
@tonivj5
Copy link

tonivj5 commented Jun 24, 2021

I come from https://github.com/EventEmitter2/EventEmitter2 and I was looking for this feature, I would like to see this built-in but I understand the inconvenients. Thanks for the workaround @developit and this gold project 🥇

@dekadentno
Copy link

Does anyone have a workin codesandbox/jsfiddle example with the provided workaround? I cannot seem to make it work.

@mangreen
Copy link

mangreen commented Nov 7, 2021

@dekadentno I have tested it. It works, but you have to modify a little bit.
Credit to @developit

<script src="https://unpkg.com/mitt/dist/mitt.umd.js"></script>
const sleep = (ms) => {
  return new Promise(resolve => setTimeout(resolve, ms));
}

const mittAsync = (all) => {
  const inst = mitt(all);
  
  inst.emitAsync = async function (type, e){
    let handlers = this.all.get(type);
    if (handlers) for (const f of handlers) await f(e);

    handlers = this.all.get('*');
    if (handlers) for (const f of handlers) await f(type, e);
  };
  
  return inst;
}

const evt = mittAsync();

evt.on('foo', async (e) => {
	await sleep(2000);
	console.log('foo', e);
});

const test = async () => {
	await evt.emitAsync('foo', { a: 'b' });
  await console.log('test');
}

test();

You can test it here https://jsfiddle.net/tr2myfwk/

@developit
Copy link
Owner

Moved to #157.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
can't Out of scope / too large / too seldom-used
Projects
None yet
Development

No branches or pull requests

6 participants