Skip to content

Instantly share code, notes, and snippets.

@subtleGradient
Created October 4, 2024 19:54
Show Gist options
  • Save subtleGradient/7d31f0d31ef08e7d30617fa2fd92bdf3 to your computer and use it in GitHub Desktop.
Save subtleGradient/7d31f0d31ef08e7d30617fa2fd92bdf3 to your computer and use it in GitHub Desktop.
/// <reference types="@frens/reframe/micro-apps" />
import ui, { clientManifest, missingClientComponents as todo } from "@frens/reframe/Micro.ClientComponents"
import React from "react"
import ReactServerDOMServer, { type BundlerConfig } from "react-server-dom-webpack/server"
import { PassThrough } from "stream"
import { withAsyncIterableTimeout } from "../AsyncIterableTimeout"
type GenUI = (props: { ui: typeof ui; todo: typeof todo }) => React.ReactNode
const CONFIG = {
timeoutMs: 500,
}
export async function* renderFromServer(RootView: GenUI, config = CONFIG) {
const root = <RootView ui={ui} todo={todo} />
yield* withAsyncIterableTimeout(renderToAsyncIterable(root, clientManifest), config.timeoutMs)
}
function renderToPipeableStream(renderable: any, clientManifest: BundlerConfig) {
return ReactServerDOMServer.renderToPipeableStream(renderable, clientManifest, {
onError: (err: unknown) => console.error("ReactServerDOMServer.renderToPipeableStream onError", err),
onPostpone: (reason: unknown) => console.warn("ReactServerDOMServer.renderToPipeableStream onPostpone", reason),
})
}
export async function* renderToAsyncIterable(renderable: any, clientManifest: BundlerConfig): AsyncIterable<string> {
const pipeableStream = renderToPipeableStream(renderable, clientManifest)
const passThrough = new PassThrough()
pipeableStream.pipe(passThrough)
const textDecoder = new TextDecoder("utf-8")
try {
for await (const chunk of passThrough) {
yield textDecoder.decode(chunk, { stream: true })
}
} finally {
yield textDecoder.decode(undefined, { stream: false })
}
}
export async function Await({ children: promise }: { children: React.ReactNode | Promise<React.ReactNode> }) {
"server only"
return await promise
}
/* eslint-disable no-inner-declarations */
class AsyncIterableTimeoutError extends Error {
constructor(message: string) {
super(message)
this.name = "TimeoutError"
}
}
export function withAsyncIterableTimeout<T>(
asyncIterable: AsyncIterable<T>,
timeoutMs: number,
abortController = new AbortController(),
): AsyncIterable<T> {
return {
async *[Symbol.asyncIterator]() {
const iterator = asyncIterable[Symbol.asyncIterator]()
while (abortController.signal.aborted === false) {
let timeoutId: ReturnType<typeof setTimeout> | undefined
const timeoutPromise = new Promise<never>((_, reject) => {
timeoutId = setTimeout(() => {
abortController.abort()
reject(new AsyncIterableTimeoutError(`Iteration timed out after ${timeoutMs}ms`))
}, timeoutMs)
})
try {
const nextPromise = iterator.next()
const racePromise = Promise.race([timeoutPromise, nextPromise])
const result = await racePromise
clearTimeout(timeoutId)
if (result.done) return
yield result.value
} catch (error) {
if (error instanceof AsyncIterableTimeoutError) return // Exit the generator on timeout
throw error // Re-throw other errors
} finally {
if (abortController.signal.aborted) {
if (typeof iterator.return === "function") await iterator.return()
// eslint-disable-next-line no-unsafe-finally
return // Exit the generator if aborted
}
}
}
},
}
}
// Usage examples:
// if (require.main === module) {
// // Example 1: Basic usage
// async function* slowCounter() {
// for (let i = 1; i <= 5; i++) {
// await new Promise((resolve) => setTimeout(resolve, 1000))
// yield i
// }
// }
// async function example1() {
// const timeoutIterable = withAsyncIterableTimeout(slowCounter(), 2500)
// for await (const item of timeoutIterable) {
// console.log("example1 received:", item)
// }
// }
// // Example 2: Using with array-like structures
// async function example2() {
// const asyncArray = (async function* () {
// for (const item of [1, 2, 3, 4, 5]) {
// yield item
// }
// })()
// const timeoutIterable = withAsyncIterableTimeout(asyncArray, 1000)
// const result = await Array.fromAsync(timeoutIterable)
// console.log("example2 result:", result)
// }
// // Example 3: Handling timeout error
// async function example3() {
// const infiniteIterable = (async function* () {
// let count = 0
// while (true) {
// await new Promise((resolve) => setTimeout(resolve, 1000))
// const value = Math.random()
// yield value
// count++
// }
// })()
// try {
// const timeoutIterable = withAsyncIterableTimeout(infiniteIterable, 3000)
// for await (const item of timeoutIterable) {
// console.log("example3 received:", item)
// }
// } catch (error) {
// if (error instanceof AsyncIterableTimeoutError) {
// console.log("Iteration timed out:", error.message)
// } else {
// console.error("Unexpected error:", error)
// }
// }
// }
// // Run examples
// async function test() {
// console.log("Example 1:")
// await example1()
// console.log("\nExample 2:")
// await example2()
// console.log("\nExample 3:")
// await example3()
// }
// await test()
// }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment