|
1 | | -# Async |
| 1 | +--- |
| 2 | +title: Redirecting... |
| 3 | +head: |
| 4 | + - - meta |
| 5 | + - http-equiv: refresh |
| 6 | + content: '0; url=https://html.kitajs.org/' |
| 7 | +--- |
2 | 8 |
|
3 | | -## Async await |
| 9 | +<script>if (typeof window !== 'undefined') window.location.replace('https://html.kitajs.org/')</script> |
4 | 10 |
|
5 | | -`async`/`await` inside components are 100% supported. |
6 | | - |
7 | | -When any child or sub child of a component tree is a `Promise<string>`, the |
8 | | -whole tree will return a promise of html string. |
9 | | - |
10 | | -If no async components are found, the result will be simply a string, and you |
11 | | -can safely cast it into a string. |
12 | | - |
13 | | -```tsx |
14 | | -import assert from 'node:assert'; |
15 | | -import { setTimeout } from 'node:timers/promises'; |
16 | | - |
17 | | -async function Async() { |
18 | | - await setTimeout(1000); // simulates work |
19 | | - return <div>Async!</div>; |
20 | | -} |
21 | | - |
22 | | -function Sync() { |
23 | | - return <div>Sync!</div>; |
24 | | -} |
25 | | - |
26 | | -const async = ( |
27 | | - <div> |
28 | | - <Async /> |
29 | | - </div> |
30 | | -); |
31 | | - |
32 | | -assert(async instanceof Promise); |
33 | | - |
34 | | -const sync = ( |
35 | | - <div> |
36 | | - <Sync /> |
37 | | - </div> |
38 | | -); |
39 | | - |
40 | | -assert(typeof sync === 'string'); |
41 | | -``` |
42 | | - |
43 | | -A `JSX.Element` will always be a string. Once a children element is a async |
44 | | -component, the entire upper tree will also be async. |
45 | | -[Learn when JSX.Element is a Promise](#why-jsxelement-is-a-promise). |
46 | | - |
47 | | -## Suspense component |
48 | | - |
49 | | -The only problem when rendering templates is that you must wait for the whole |
50 | | -template to be rendered before sending it to the client. This is not a problem |
51 | | -for small templates, but it can be a problem for large templates. |
52 | | - |
53 | | -To solve this problem, we provide a `Suspense` component that combined with |
54 | | -`renderToStream()` rendering method, will stream a fallback component while it |
55 | | -waits for his children to be rendered. |
56 | | - |
57 | | -```tsx |
58 | | -import { Suspense, renderToStream } from '@kitajs/html/suspense'; |
59 | | - |
60 | | -function renderUserPage(rid: number | string) { |
61 | | - return ( |
62 | | - <Suspense |
63 | | - rid={rid} |
64 | | - fallback={<div>Loading username...</div>} |
65 | | - catch={(err) => <div>Error: {String(err)}</div>} |
66 | | - > |
67 | | - <MyAsyncComponent /> |
68 | | - </Suspense> |
69 | | - ); |
70 | | -} |
71 | | - |
72 | | -// Html is a string readable stream that can be piped to the client |
73 | | -const html = renderToStream(renderUserPage); |
74 | | -``` |
75 | | - |
76 | | -<br /> |
77 | | - |
78 | | -<!-- > [!NOTE] |
79 | | -> The `renderToStream()` is returns a native node/bun stream, head over to our |
80 | | -> [suspense-server](./examples/suspense-server.tsx) example to see how to use it |
81 | | -> with node:http, Express or Fastify servers. --> |
82 | | - |
83 | | -<br /> |
84 | | - |
85 | | -The above example would render `<div>Loading username...</div>` while waiting |
86 | | -for the `MyAsyncComponent` to be rendered. |
87 | | - |
88 | | -When using `Suspense`, you cannot just call the component and get the html |
89 | | -string, you need to use the `renderToStream` function to get a stream that can |
90 | | -be piped to the client with updates. Otherwise, the fallback would render |
91 | | -forever. |
92 | | - |
93 | | -As the result of any JSX component is always a string, you must use the `rid` |
94 | | -provided by `renderToStream` into all your suspense components, this way we can |
95 | | -identify which suspense is for which request and be able to render concurrent |
96 | | -requests. |
97 | | - |
98 | | -Suspense also accepts async fallbacks, but it blocks rendering until the |
99 | | -fallback is resolved. |
100 | | - |
101 | | -```tsx |
102 | | -import { Suspense } from '@kitajs/html/suspense'; |
103 | | - |
104 | | -function renderTemplate(rid: number | string) { |
105 | | - return ( |
106 | | - <Suspense |
107 | | - rid={rid} |
108 | | - fallback={<MyAsyncFallback />} |
109 | | - catch={(err) => <div>Error: {String(err)}</div>} |
110 | | - > |
111 | | - <MyAsyncComponent /> |
112 | | - </Suspense> |
113 | | - ); |
114 | | -} |
115 | | -``` |
116 | | - |
117 | | -The above example would only return anything after `MyAsyncFallback` is |
118 | | -resolved. To catch async fallback errors, you must wrap it into a |
119 | | -[`ErrorBoundary`](#error-boundaries). |
120 | | - |
121 | | -<br /> |
122 | | - |
123 | | -## Error boundaries |
124 | | - |
125 | | -The same way as promises must be awaited to resolve its own html, errors must be |
126 | | -caught. Outside of [suspense](#suspense-component) components, you can use the |
127 | | -provided error boundaries to catch errors. |
128 | | - |
129 | | -```tsx |
130 | | -import { ErrorBoundary } from '@kitajs/html/error-boundary'; |
131 | | - |
132 | | -function renderTemplate() { |
133 | | - return ( |
134 | | - <ErrorBoundary catch={(err) => <div>Error: {String(err)}</div>}> |
135 | | - <MyAsyncComponent /> |
136 | | - </ErrorBoundary> |
137 | | - ); |
138 | | -} |
139 | | - |
140 | | -// If MyAsyncComponent throws an error, it will render <div>Error: ...</div> |
141 | | -const html = await renderTemplate(); |
142 | | -``` |
143 | | - |
144 | | -Error boundaries will only work for errors thrown inside async components, for |
145 | | -sync components you must use `try`/`catch`. |
146 | | - |
147 | | -```tsx |
148 | | -function MySyncComponent() { |
149 | | - try { |
150 | | - const data = syncDbQuery(); |
151 | | - return <Username name={data.username} />; |
152 | | - } catch (err) { |
153 | | - return <div>Error: {String(err)}</div>; |
154 | | - } |
155 | | -} |
156 | | -``` |
157 | | - |
158 | | -Error boundaries outside suspense components will only catch errors thrown by |
159 | | -the fallback component. You must use the Suspense's `catch` property to handle |
160 | | -errors thrown by its children components. |
161 | | - |
162 | | -```tsx |
163 | | -import { ErrorBoundary } from '@kitajs/html/error-boundary'; |
164 | | -import { renderToStream, Suspense } from '@kitajs/html/suspense'; |
165 | | - |
166 | | -function renderTemplate(rid: number | string) { |
167 | | - return ( |
168 | | - <ErrorBoundary catch={<div>Only catches fallback errors</div>}> |
169 | | - <Suspense |
170 | | - rid={rid} |
171 | | - fallback={<MyAsyncFallback />} |
172 | | - catch={<div>Catches children errors</div>} |
173 | | - > |
174 | | - <MyAsyncComponent /> |
175 | | - </Suspense> |
176 | | - </ErrorBoundary> |
177 | | - ); |
178 | | -} |
179 | | - |
180 | | -const html = renderToStream(renderTemplate); |
181 | | -``` |
182 | | - |
183 | | -The above example would render `<div>Children error</div>` if `MyAsyncComponent` |
184 | | -throws an error, or `<div>fallback error</div>` if `MyAsyncFallback` throws an |
185 | | -error. If both throws an error, the first error will be changed to the second |
186 | | -error as soon as the children error is thrown. |
187 | | - |
188 | | -<br /> |
189 | | - |
190 | | -## `JSX.Element` might be a `Promise`? |
191 | | - |
192 | | -> [!NOTE] |
193 | | -> |
194 | | -> Until [#14729](https://github.com/microsoft/TypeScript/issues/14729) gets |
195 | | -> implemented, you need to manually cast `JSX.Element` into strings if you are |
196 | | -> sure there is no inner async components in your component tree. |
197 | | -
|
198 | | -<br /> |
199 | | - |
200 | | -JSX elements are mostly strings everywhere. |
201 | | - |
202 | | -However, as the nature of this package, once a children element is a async |
203 | | -component, the entire upper tree will also be async. Unless you are sure that no |
204 | | -other component in your entire codebase is async, you should always handle both |
205 | | -string and promise cases. |
206 | | - |
207 | | -```tsx |
208 | | -// It may or may not have inner async components. |
209 | | -const html = <MyAsyncComponent />; |
210 | | - |
211 | | -if (html instanceof Promise) { |
212 | | - // I'm a promise, I should be awaited |
213 | | - console.log(await html); |
214 | | -} else { |
215 | | - // I'm a string, I can be used as is |
216 | | - console.log(html); |
217 | | -} |
218 | | -``` |
219 | | - |
220 | | -## Api |
221 | | - |
222 | | -### `renderToStream` |
223 | | - |
224 | | -Transforms a component tree who may contain `Suspense` components into a stream |
225 | | -of HTML. |
226 | | - |
227 | | -There's two ways of using `renderToStream`: |
228 | | - |
229 | | -With a `rid` parameter, to identify which suspense is for which request. |
230 | | - |
231 | | -If you are using a framework or some kind of code that already generates a |
232 | | -unique request id per request, just like |
233 | | -[Fastify's request.id](https://fastify.dev/docs/latest/Reference/Request/), you |
234 | | -can simply renders your component tree with it and pass it to `renderToStream`. |
235 | | - |
236 | | -```tsx |
237 | | -import { renderToStream } from '@kitajs/html/suspense'; |
238 | | - |
239 | | -// If you are literally doing this, please use the 2nd way XD |
240 | | -let requestId = 0; |
241 | | - |
242 | | -function handleRequest() { |
243 | | - const rid = requestId++; |
244 | | - const html = renderToStream(<LayoutWithSuspense rid={rid} />, rid); |
245 | | - // pipe html to the client |
246 | | -} |
247 | | -``` |
248 | | - |
249 | | -If you do not have easy access to a unique request id, you can pass a callback |
250 | | -as the first parameter to `renderToStream` that will be called with the request |
251 | | -id. |
252 | | - |
253 | | -```tsx |
254 | | -import { renderToStream } from '@kitajs/html/suspense'; |
255 | | - |
256 | | -function handleRequest() { |
257 | | - const html = renderToStream((rid) => <LayoutWithSuspense rid={rid} />); |
258 | | - // pipe html to the client |
259 | | -} |
260 | | -``` |
261 | | - |
262 | | -### `renderToString` |
263 | | - |
264 | | -Just like `renderToStream`, but only resolves when the whole tree is rendered. |
265 | | - |
266 | | -::: warning |
267 | | - |
268 | | -This method completely throws away the suspense feature, and will render the |
269 | | -whole tree before returning the string. |
270 | | - |
271 | | -This is only useful for testing environments or when you don't care about the |
272 | | -time it takes to render the whole tree. |
273 | | - |
274 | | -::: |
275 | | - |
276 | | -```tsx |
277 | | -import { renderToString } from '@kitajs/html/suspense'; |
278 | | - |
279 | | -async function handleRequest() { |
280 | | - const html: string = await renderToString((rid) => ( |
281 | | - <LayoutWithSuspense rid={rid} /> |
282 | | - )); |
283 | | -} |
284 | | -``` |
| 11 | +This page has moved. Redirecting to [html.kitajs.org](https://html.kitajs.org/)... |
0 commit comments