Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Typed async events with sequenced and parallel dispatching

NotificationsYou must be signed in to change notification settings

hazae41/plume

Repository files navigation

Typed async events with sequenced and parallel dispatching

npm i @hazae41/plume

Node Package 📦

Features

Current features

  • 100% TypeScript and ESM
  • No external dependency
  • Rust-like patterns
  • Type-safe event dispatching and listening
  • Event listeners can return values
  • Sequenced and parallel dispatching
  • Wait for events with composition

Usage

Emitters

/** * 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

for(constlisteneroflisteners){constreturned=awaitlistener(...)if(returned.isSome())returnreturnedcontinue}returnnewNone()
/** * 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"))

Parallel dispatching

Parallel listening usingpassive: true

Both listeners will be called at the same time

Their result will be retrieved withPromise.all

constpromises=newArray<Promise<...>>()for(constlisteneroflisteners)promises.push(listener(...))constreturneds=awaitPromise.all(promises)for(constreturnedofreturneds)if(returned.isSome())returnreturnedreturnnewNone()
/** * 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

import{Future}from"@hazae41/future"asyncfunctionrequestAndWaitOrCloseOrErrorOrSignal(id:number,request:string,signal:AbortSignal):Promise<string>{constsocket=newMySocket()socket.send({ id,text:request})constresponse=awaitPlume.waitOrCloseOrErrorOrSignal(socket,"message",async(future:Future<string>,message)=>{if(message.id===id){future.resolve(message.text)returnnewNone()}returnnewNone()},signal)returnresponse}

[8]ページ先頭

©2009-2025 Movatter.jp