Bustin Base
Bustin Base•10mo ago

How can I do server sent events on the client side in deno?

I have a server api which is doing server sent events correctly, I can see it in curl doing what I would expect. One the calling side I have a simple cli written in deno and I'm trying to use an EventSource, which is not a requirement. If there is an easier way I'm all for it. The downside of the EventSource is that it is an event handler api, where you subscribe to on('message', handler) and I'm looking for an async generator api. If I just use the EventSource as is, it appears to not work at all, the process just exits right away as if its not taking out a ref(). If I convert it into an async generator then I"m getting a "Top-level await promise never resolved" error. I'm wondering if anyone has any simple examples of doing server sent events in deno client side? Using EventSource is not required at all.
3 Replies
Deno AI Helper
Deno AI Helper•10mo ago
To handle Server-Sent Events (SSE) on the client side in Deno, you can indeed use the EventSource API, which is a standard web API for receiving push notifications from a server in the form of events. The documentation provides an example of how to use EventSource in the context of a server, but the client-side usage would be similar. Here's a basic example of how you might use EventSource in Deno to listen for messages from a server:
const url = "http://your-sse-server.com/sse";
const eventSource = new EventSource(url);

eventSource.onmessage = (event) => {
console.log("New message:", event.data);
};

eventSource.onerror = (error) => {
console.error("EventSource failed:", error);
eventSource.close();
};
const url = "http://your-sse-server.com/sse";
const eventSource = new EventSource(url);

eventSource.onmessage = (event) => {
console.log("New message:", event.data);
};

eventSource.onerror = (error) => {
console.error("EventSource failed:", error);
eventSource.close();
};
However, since you're looking for an async generator API rather than an event handler API, you can wrap the EventSource in an async generator function like this:
async function* eventStream(url) {
const eventSource = new EventSource(url);

while (true) {
const event = await new Promise((resolve, reject) => {
eventSource.onmessage = resolve;
eventSource.onerror = reject;
});

if (event.type === 'error') {
eventSource.close();
throw event; // or handle the error as you see fit
}

yield event.data;
}
}

// Usage
const url = "http://your-sse-server.com/sse";
for await (const message of eventStream(url)) {
console.log("New message:", message);
}
async function* eventStream(url) {
const eventSource = new EventSource(url);

while (true) {
const event = await new Promise((resolve, reject) => {
eventSource.onmessage = resolve;
eventSource.onerror = reject;
});

if (event.type === 'error') {
eventSource.close();
throw event; // or handle the error as you see fit
}

yield event.data;
}
}

// Usage
const url = "http://your-sse-server.com/sse";
for await (const message of eventStream(url)) {
console.log("New message:", message);
}
This async generator will yield messages as they arrive from the server. When using this pattern, make sure to handle the cleanup properly, such as closing the EventSource when you're done with it, to prevent memory leaks. Regarding the issue where the process exits right away, it's possible that Deno is not recognizing that
Deno AI Helper
Deno AI Helper•10mo ago
there's an open connection that should keep the process alive. This could be due to the EventSource not taking out a ref() on the event loop. Wrapping it in an async generator as shown above should keep the process alive as long as the generator is being iterated over. If you're encountering a "Top-level await promise never resolved" error, it could be due to the way the promises are being handled within the async generator. Ensure that the promises are being resolved or rejected correctly, and that any errors from the EventSource are being handled. Remember to replace "http://your-sse-server.com/sse" with the actual URL of your SSE server. This should give you a starting point for using SSE in a Deno client application with an async generator pattern. 🦕
Bustin Base
Bustin BaseOP•10mo ago
Can you give an example with the typescript typing, assuming the message data is of type Example = { id: string } I guess its just as simple as adding new Promise<MessageEvent<Example>> to the promise