Closed jAlpedrinha closed 8 years ago
Events are generally fire-and-forget, so it doesn't really make sense to have any sort of asynchronous-specific support. A function is a function. You can call it whenever you want, and deal with the fallout how you will. Moreover, this is a pretty cut-and-dry implementation of nodejs-style EEs, and I don't really want to stray too far from that.
In other words, I'm not really interested in supporting any features beyond what's already implemented, but I can still talk use case with you, if you think I might have a suggestion in terms of alternate patterns.
(There's also the odd chance that I'm missing something due to unfamiliarity with how python implements futures/etc)
Not sure with what you mean with fire-and-forget, since EventEmitter has synchronous behaviour.
emit
only returns execution when all handlers are called instead of using something like yield from
handler which would allow to really fire the event and forget what will happen next, without having to wait for the handler to end execution.
I understand that this has a natural solution in nodejs by using setImmediate, which isn't as natural in python, since there isn't by default an event loop.
If you feel like sticking to EventEmitter like it exists in nodejs, there's no problem.
I don't follow. What I mean is that no result from any actions your EE takes are retained, so the function handler can easily kick off an asynchronous action no problem. Like in javascript you would just do
ee.on('event', (data) => {
// This schedules an async action, but the actual meat of this function beyond validation
// happens on a subsequent tick, and the EE's handler doesn't do anything with the
// result (this is what I mean by "fire and forget")
doSomeAsyncThing((err) => {
if (err) throw err; // Whatever
console.log('did the thing');
});
});
and you're off to the races! I have to assume that in python you would do something similar. Looking at [https://docs.python.org/3/library/asyncio-task.html#example-hello-world-coroutine](python 3's docs for asyncio) I assume you'd just make some calls to loop
with some coroutines.
But, out of curiosity, what would an example of your proposal Look Like? Maybe seeing a code sample will make this make more sense.
I'll provide you an example and explain the nature of async
and await
.
import asyncio
import pyee
ee = pyee.EventEmitter()
async def sleep(seconds):
print('abc')
await asyncio.sleep(seconds)
print('dow')
ee.on('test', sleep)
async def start():
print("I will emit")
ee.emit('test', 2)
print("I have emitted")
await asyncio.sleep(10)
loop = asyncio.get_event_loop()
loop.set_debug(True)
loop.run_until_complete(start())
print ('done')
If you run this snippet you can see that you'll receive an error because I've set_debug
to True
on the event loop, if you disable you'll just get the same message as a warning.
The reason for this is that an async
function is a coroutine which returns the execution to its caller after it is invoked returning a future. If this future is not await
ed (or yield
ed) the code will simply not run.
This doesn't need to occur imediately, you can retrieve the future and wait for it later on, even though the stream of execution will temporarily stop (which seems like synchronous code) the reality is that if there is any other stream of execution running concurrently it will be able to continue while this is waiting.
This is highly similar to the ES proposal here
My proposal to change this would be to have an emit_async
that would be a coroutine that would await
handlers instead of just invoking them, that could itself be await
ed. There would also be some changes on the once
wrapper that would still work as it does now, but would behave differently when the handler is a coroutine.
I understand if you don't want to pursue this path, and in that case I will be more than happy to implement a version of this code. I'm just sharing this because I really love the observer pattern, and this was one of the cleanest implementations of it that I've found. But since I'm embracing the asynchronous side of python, which makes sense but breaks with some of the current establishment, I'd like to contribute with your effort.
So what you're saying is that we'd "like" to see the following (because async/await -> futures is the async io abstraction in python)
I will emit
abc
I have emitted
dow
done
but right now it prints
I will emit
abc
dow
I have emitted
done
?
Is there a way to switch on whether something is a couroutine/future-generating-function rather than a Boring Function, so that we don't need an alternate implementation? Is there a way to explicitly make it so you don't have to await (or maybe even can't await) the ee call (it sounds like you might have to in this proposal)?
You made comparisons to JavaScript's async/await, which I believe behind the scenes are just packing/unpacking promises (and yield/generators with iterators). Arguably, shouldn't your function handlers just be more explicit about this? I haven't heard of anyone making similar proposals for node, and promisified-by-default core modules are certainly being discussed.
This looks relevant http://stackoverflow.com/questions/37278647/fire-and-forget-python-async-await
I want to use https://docs.python.org/3.5/library/types.html#types.CoroutineType to switch on whether @ee.on is decorating a coroutine or a regular function (note: do I want to switch on generators for completeness?), and then use https://docs.python.org/3/library/asyncio-task.html#asyncio.ensure_future (or possibly an overridable version of this?) with a possible override to schedule coroutines to run.
Something like,
@ee.on('fire')
async def fire_and_forget():
await async_things()
// from somewhere
@ee.emit('fire')
loop.close()
I think? Still learning how the event loop works.
Have you considered supporting asynchronous functions as handlers or even creating an asynchronous emit function ?
This would only be supported in 3.4+, but in my use case is a must. If you're interested I could elaborate a bit more on the requirements.
I'd be available to work with you on this, never breaking backwards compatibility but making such methods available for 3.4+ adopters.
WDYT?