You signed in with another tab or window.Reload to refresh your session.You signed out in another tab or window.Reload to refresh your session.You switched accounts on another tab or window.Reload to refresh your session.Dismiss alert
/** * Events are described as functions that can accept multiple parameters and return something */typeMyEvents={/** * This will handle a request and return a response */request:(data:string)=>string,/** * This will handle a close and return nothing */close:(reason?:unknown)=>void,/** * This will handle an error and return nothing */error:(reason?:unknown)=>void,}
classMyObject{/** * Composition over inheritance */readonlyevents=newSuperEventTarget<MyEvents>()/** * Dispatch an "error" event with a reason **/asynconError(reason?:unknown){awaitthis.events.emit("error",reason)}/** * Dispatch a "close" event without a reason **/asynconClose(){awaitthis.event.emit("close")}/** * Dispatch a "request" event and return the returned response */asyncrequest(data:string):string{constresponse=awaitthis.events.emit("request",data)/** * When a listener has returned something */if(response.isSome())returnresponse.get()/** * When no listener has returned */thrownewError(`Unhandled`)}}
Listeners
constobject=newMyObject()object.on("request",(request:string)=>{if(request==="hello")/** * Return something and skip next listeners */returnnewSome("world")/** * Unhandled by this listener */returnnewNone()})object.on("request",(request:string)=>{if(request==="it")/** * Return something and skip next listeners */returnnewSome("works")/** * Unhandled by this listener */returnnewNone()})object.on("request",(request:string)=>{if(request==="have")/** * Return something and skip next listeners */returnnewSome("fun")/** * Unhandled by this listener */returnnewNone()})
Sequenced dispatching (default)
You can use sequenced listening usingpassive: false (orpassive: undefined)
The listeners will be called one after the other
When a listener returns something, it will skip all other listeners
/** * This listener will be called first */myObject.events.on("message",async(message:string)=>{awaitdoSometing(message)returnnewSome(1)},{passive:false})/** * This listener will be skipped */myObject.events.on("message",async(message:string)=>{awaitdoSometing2(message)returnnewSome(2)},{passive:false})/** * Some(1) */console.log(awaitmyObject.emit("message","hello world"))
/** * This listener will be called first */myObject.events.on("message",async(message:string)=>{awaitdoSometing(message)returnnewSome(1)},{passive:true})/** * This listener will be called too */myObject.events.on("message",async(message:string)=>{awaitdoSometing(e.data)returnnewSome(2)},{passive:true})/** * Some(1) */console.log(awaitmyObject.emit("message","hello world"))
Waiting for an event
In this example we have a target with asend() method and amessage event
We want to send a message with some ID and wait for a reply with the same ID, skipping replies with other ID
Waiting is always done usingpassive: true
import{Future}from"@hazae41/future"asyncfunctionrequestAndWait(id:number,request:string):Promise<string>{constsocket=newMySocket()socket.send({ id,text:request})constresponse=awaitsocket.wait("message",async(future:Future<string>,message)=>{/** * Only wait for a message with the same id */if(message.id===id){/** * Resolve with the text */future.resolve(message.text)/** * Do not skip other listeners */returnnewNone()}/** * Do not skip other listeners */returnnewNone()})returnresponse}
Composing waiters with automatic disposal
Same as above but this time the event is raced with other events in a composable way
When one event is resolved or rejected, it will stop listening to the other (it is disposed by theusing keyword)
import{Future}from"@hazae41/future"asyncfunctionrequestAndWaitOrClose(id:number,request:string):Promise<string>{constsocket=newMySocket()socket.send({ id,text:request})/** * Resolve on message */ usingevent=socket.wait("message",async(future:Future<string>,message)=>{if(message.id===id){future.resolve(message.text)returnnewNone()}returnnewNone()})/** * Reject on close */ usingclose=socket.wait("close",(future:Future<never>)=>{future.reject(newError("Closed"))returnnewNone()})returnawaitPromise.race([event,close])}
Plume provides some helper functions for doing this with fewer lines of code
import{Future}from"@hazae41/future"asyncfunctionrequestAndWaitOrCloseOrErrorOrSignal(id:number,request:string,signal:AbortSignal):Promise<string>{constsocket=newMySocket()socket.send({ id,text:request})/** * Resolve on message */ usingevent=socket.wait("message",async(future:Future<string>,message)=>{if(message.id===id){future.resolve(message.text)returnnewNone()}returnnewNone()})/** * Reject on signal */ usingabort=Plume.AbortedError.waitOrThrow(signal)/** * Reject on error (only if the target has an "error" event) */ usingerror=Plume.ErroredError.waitOrThrow(socket)/** * Reject on close (only if the target has a "close" event) */ usingclose=Plume.ClosedError.waitOrThrow(socket)returnawaitPromise.race([event,close,error,abort])}
And it provides helpers for common error-close-signal patterns