Skip to main content

Introduction

what is ocev

ocev is an event library designed to simplify the complexity of event processing, while supporting promise/stream mode to handle events. supporting all events of proxy web elements, and processing with ocev api. all api are maximized support typescript, providing the most complete type prompt

Install

  npm install ocev

Basic Usage

import { SyncEvent } from "ocev"

// define event type
type EventHandlerMap = {
event1: (arg1: string, arg2: number) => void
event2: (arg1: number, arg2: string) => void
}

const syncEvent = SyncEvent.new<EventHandlerMap>()

queueMicrotask(() => {
syncEvent.emit("event1", "1", 2)
syncEvent.emit("event2", 3, "4")
})

// register
const cancel = syncEvent
.on("event1", (arg1, arg2) => {})
.once("event2", (arg1, arg2) => {})
.on("event1", (arg1, arg2) => {}, {
debounce: {
waitMs: 200,
maxWaitMs: 500,
},
})

// cancel()

// waitUtil event emit
await syncEvent.waitUtil("event1")

// create event stream
const eventStream = syncEvent.createEventStream(["event1", "event2"])

Reasons for using ocev

From the above example, you can see that ocev is essentially a (pub/sub) library, but ocev can also proxy all events of web element, and use ocev to handle all events with promise/stream.

tip

ocev has two class,SyncEvent,EventProxy, the following example is mainly based on EventProxy

ocev has the following characteristics

1. Simplifying web event handling

I've always felt that web event handling is too complex, and if you're using react, you're probably going to write this code.

useEffect(() => {
const callback = () => {}
target.addEventListener("event", callback) // any event target

return () => {
target.removeEventListener("event", callback)
}
}, [target])

for multiple events

useEffect(() => {
const callback1 = () => {}
target.addEventListener("event1", callback1)

const callback2 = () => {}
target.addEventListener("event2", callback2)

// ....

return () => {
target.removeEventListener("event1", callback1)
target.removeEventListener("event2", callback2)
// ....
}
}, [target])

You have to clean up as many as you register, which is very cumbersome to write.

If you are using ocev, your code will be something like this, infinite calls, one-time cleanup

import { EventProxy } from "ocev"

useEffect(() => {
return EventProxy.new(target)
.on("event1", (...args) => {}) // Support complete type hints
.once("event2", (...args) => {})
.on("event3", (...args) => {})
}, [target])

ocev's method on/once returns a clean function, which can be called once,on as an object. For more details, please see the documentation.

tip

All examples in this section are based on EventProxy, which is an encapsulation of SyncEvent. For more information on both, see the documentation.

2. Promise support

Consider a scenario where you want to establish a websocket connection, wait for the connection to open, set the maximum wait time for the connection, and then handle messages and exceptions. To ensure the correct release of resources, you might write the following code

async function setupWebSocket(
url: string,
successCallback: (ws: WebSocket) => void,
errorCallback: (err: Error) => void,
timeout: number
) {
const ws = new WebSocket(url)

const timeID = setTimeout(() => {
errorCallback(new Error("timeout"))
ws.removeEventListener("open", onOpen)
ws.removeEventListener("error", onError)
}, timeout)

function onOpen() {
successCallback(ws)
clearTimeout(timeID)
}

function onError() {
errorCallback(new Error("can't connect to server"))
clearTimeout(timeID)
}

ws.addEventListener("open", onOpen)
ws.addEventListener("error", onError)
}

ocev supports Promise to handle events. If you use ocev to handle events, the code will be like this

import { EventProxy } from "ocev"

async function setupWebSocket(url: string, timeout: number) {
const ws = new WebSocket(url)
// Wait for the 'open' event to trigger or timeout throws an exception
await EventProxy.new(ws).waitUtil("open", { timeout })

return ws
}

Promise makes event handling simple and elegant, and using Promise to process code makes logic clearer

Take it a step further and see how to implement message processing (Stream) with ocev

import { EventProxy } from "ocev"

async function setupWebSocket(url: string, timeout: number) {
const ws = EventProxy.new(new WebSocket(url))

await ws.waitUtilRace([
{ event: "open", timeout },
{
event: "error",
mapToError: () => new Error("websocket connect error"),
},
])

// transform to event stream
const eventStream = ws.createEventStream(["close", "message", "error"])
// another way create ReadableStream
// const readableStream = ws.createEventReadableStream(["close", "message", "error"])

// All events are pushed into a queue and triggered in turn
for await (const { event, value } of eventStream) {
switch (event) {
case "error": {
throw Error("websocket connect error")
}
case "close": {
throw Error("websocket connection closed")
}
case "message": {
// 支持类型提示
const message = value[0].data
// handle message
break
}
default:
throw new Error("unreachable")
}
}
}

With asynchronous iterators, you can convert events into streams, and when processing message streams, you can also use strategy to drop some messages to deal with backpressure problems.

With Promise/Stream, the code becomes legible, and when you convert all the code to async/await, you can handle the reconnection logic like this

let reconnectCount = 0
for (;;) {
try {
await setupWebSocket("", 1000)
} catch (error) {
reconnectCount += 1
}
}

If you want to establish a WebRTC connection, you can write something like this

import { EventProxy } from "ocev"

async function connect(timeout: number) {
const connection = new RTCPeerConnection()

await EventProxy.new(connection).waitUtil("connectionstatechange", {
timeout,
// resolve only if where returns true
where: (ev) => connection.connectionState === "connected",
})

return connection
}

For more waitUtil configuration, please see the documentation;

3. Observe all events of a web object

Do you know what events 'video' triggers when it plays? Use ocev to directly observe all events of a web object

import { EventProxy } from "ocev"
// support complete type prompt !
EventProxy.new(videoDom, { proxyAllEvent: true }).any((eventName, ...args) => {
console.log(eventName)
})

Open the console and you will see all the events triggered by video and its order

Almost all web elements can be proxied by EventProxy.

More

The above is just a partial API usage of ocev, if you want to know more, please see the documentation

If you have good suggestions and ideas, welcome pr and issue