-
Notifications
You must be signed in to change notification settings - Fork 47.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Flight] Encode ReadableStream and AsyncIterables #28847
Conversation
This is consistent with other Iterables.
This was already done in Fiber but not in Fizz/Flight.
…ue it For AsyncIterables, since we control the promises, we can instead let the consumer peak ahead in that case.
This helps us avoid outlining a model if it's synchronously available.
fb5a64b
to
561970b
Compare
561970b
to
0fddb4e
Compare
This adds support in Flight for serializing four kinds of streams: - `ReadableStream` with objects as a model. This is a single shot iterator so you can read it only once. It can contain any value including Server Components. Chunks are encoded as is so if you send in 10 typed arrays, you get the same typed arrays out on the other side. - Binary `ReadableStream` with `type: 'bytes'` option. This supports the BYOB protocol. In this mode, the receiving side just gets `Uint8Array`s and they can be split across any single byte boundary into arbitrary chunks. - `AsyncIterable` where the `AsyncIterator` function is different than the `AsyncIterable` itself. In this case we assume that this might be a multi-shot iterable and so we buffer its value and you can iterate it multiple times on the other side. We support the `return` value as a value in the single completion slot, but you can't pass values in `next()`. If you want single-shot, return the AsyncIterator instead. - `AsyncIterator`. These gets serialized as a single-shot as it's just an iterator. `AsyncIterable`/`AsyncIterator` yield Promises that are instrumented with our `.status`/`.value` convention so that they can be synchronously looped over if available. They are also lazily parsed upon read. We can't do this with `ReadableStream` because we use the native implementation of `ReadableStream` which owns the promises. The format is a leading row that indicates which type of stream it is. Then a new row with the same ID is emitted for every chunk. Followed by either an error or close row. `AsyncIterable`s can also be returned as children of Server Components and then they're conceptually the same as fragment arrays/iterables. They can't actually be used as children in Fizz/Fiber but there's a separate plan for that. Only `AsyncIterable` not `AsyncIterator` will be valid as children - just like sync `Iterable` is already supported but single-shot `Iterator` is not. Notably, neither of these streams represent updates over time to a value. They represent multiple values in a list. When the server stream is aborted we also close the underlying stream. However, closing a stream on the client, doesn't close the underlying stream. A couple of possible follow ups I'm not planning on doing right now: - [ ] Free memory by releasing the buffer if an Iterator has been exhausted. Single shots could be optimized further to release individual items as you go. - [ ] We could clean up the underlying stream if the only pending data that's still flowing is from streams and all the streams have cleaned up. It's not very reliable though. It's better to do cancellation for the whole stream - e.g. at the framework level. - [ ] Implement smarter Binary Stream chunk handling. Currently we wait until we've received a whole row for binary chunks and copy them into consecutive memory. We need this to preserve semantics when passing typed arrays. However, for binary streams we don't need that. We can just send whatever pieces we have so far. DiffTrain build for [7909d8e](7909d8e)
While the protocol supports all four, here's my recommendations for how to use it. Mainly stick to binary Only use If you use it, it needs to be only passed through render without iterated and then passed to an event handler or useEffect, that knows how to deal with it. That's the same recommendation for any mutable object nested inside. React doesn't have strong recommendations for how to deal with this but one convention is to wrap it in a For things that needs to be read during render, use a multi-shot I also recommend never really using the non-binary version of You can also use |
Summary: This sync includes the changes from: - D56103750 - [TODO] A shim for SECRET_INTERNALS This sync includes the following changes: - **[b5e5ce8e0](facebook/react@b5e5ce8e0 )**: Update ReactNativeTypes for root options (part 2) ([#28857](facebook/react#28857)) //<Ricky>// - **[da6ba53b1](facebook/react@da6ba53b1 )**: [UMD] Remove umd builds ([#28735](facebook/react#28735)) //<Josh Story>// - **[0c245df1d](facebook/react@0c245df1d )**: Complete the typo fix ([#28856](facebook/react#28856)) //<Sebastian Silbermann>// - **[f82051d7a](facebook/react@f82051d7a )**: console test utils fix: match entire string, not just first letter ([#28855](facebook/react#28855)) //<Andrew Clark>// - **[4ca20fd36](facebook/react@4ca20fd36 )**: Test top level fragment inside lazy semantics ([#28852](facebook/react#28852)) //<Sebastian Markbåge>// - **[c0cf7c696](facebook/react@c0cf7c696 )**: Promote ASYNC_ITERATOR symbol to React Symbols ([#28851](facebook/react#28851)) //<Sebastian Markbåge>// - **[657428a9e](facebook/react@657428a9e )**: Add ReactNativeTypes for root options ([#28850](facebook/react#28850)) //<Ricky>// - **[7909d8eab](facebook/react@7909d8eab )**: [Flight] Encode ReadableStream and AsyncIterables ([#28847](facebook/react#28847)) //<Sebastian Markbåge>// - **[13eb61d05](facebook/react@13eb61d05 )**: Move enableUseDeferredValueInitialArg to canary ([#28818](facebook/react#28818)) //<Andrew Clark>// - **[8afa144bd](facebook/react@8afa144bd )**: Enable flag disableClientCache ([#28846](facebook/react#28846)) //<Jan Kassens>// - **[734956ace](facebook/react@734956ace )**: Devtools: Add support for useFormStatus ([#28413](facebook/react#28413)) //<Sebastian Silbermann>// - **[17e920c00](facebook/react@17e920c00 )**: [Flight Reply] Encode Typed Arrays and Blobs ([#28819](facebook/react#28819)) //<Sebastian Markbåge>// - **[0347fcd00](facebook/react@0347fcd00 )**: Add on(Caught|Uncaught|Recoverable) opts to RN ([#28836](facebook/react#28836)) //<Ricky>// - **[c113503ad](facebook/react@c113503ad )**: Flush direct streams in Bun ([#28837](facebook/react#28837)) //<Kenta Iwasaki>// - **[9defcd56b](facebook/react@9defcd56b )**: Remove redundant props assign ([#28829](facebook/react#28829)) //<Sebastian Silbermann>// - **[ed4023603](facebook/react@ed4023603 )**: Fix mistaken "react-server" condition ([#28835](facebook/react#28835)) //<Sebastian Markbåge>// - **[c8a035036](facebook/react@c8a035036 )**: [Fizz] hoistables should never flush before the preamble ([#28802](facebook/react#28802)) //<Josh Story>// - **[4f5c812a3](facebook/react@4f5c812a3 )**: DevTools: Rely on sourcemaps to compute hook name of built-in hooks in newer versions ([#28593](facebook/react#28593)) //<Sebastian Silbermann>// - **[435415962](facebook/react@435415962 )**: Backwards compatibility for string refs on WWW ([#28826](facebook/react#28826)) //<Jack Pope>// - **[608edcc90](facebook/react@608edcc90 )**: [tests] add `assertConsole<method>Dev` helpers ([#28732](facebook/react#28732)) //<Ricky>// - **[da69b6af9](facebook/react@da69b6af9 )**: ReactDOM.requestFormReset ([#28809](facebook/react#28809)) //<Andrew Clark>// - **[374b5d26c](facebook/react@374b5d26c )**: Scaffolding for requestFormReset API ([#28808](facebook/react#28808)) //<Andrew Clark>// - **[41950d14a](facebook/react@41950d14a )**: Automatically reset forms after action finishes ([#28804](facebook/react#28804)) //<Andrew Clark>// - **[dc6a7e01e](facebook/react@dc6a7e01e )**: [Float] Don't preload images inside `<noscript>` ([#28815](facebook/react#28815)) //<Josh Story>// - **[3f947b1b4](facebook/react@3f947b1b4 )**: [tests] Assert scheduler log empty in internalAct ([#28737](facebook/react#28737)) //<Ricky>// - **[bf09089f6](facebook/react@bf09089f6 )**: Remove Scheduler.log from ReactSuspenseFuzz-test ([#28812](facebook/react#28812)) //<Ricky>// - **[84cb3b4cb](facebook/react@84cb3b4cb )**: Hardcode disableIEWorkarounds for www ([#28811](facebook/react#28811)) //<Ricky>// - **[2243b40ab](facebook/react@2243b40ab )**: [tests] assertLog before act in useEffectEvent ([#28763](facebook/react#28763)) //<Ricky>// - **[dfc64c6e3](facebook/react@dfc64c6e3 )**: [tests] assertLog before act in ReactUse ([#28762](facebook/react#28762)) //<Ricky>// - **[42eff4bc7](facebook/react@42eff4bc7 )**: [tests] Fix assertions not flushed before act ([#28745](facebook/react#28745)) //<Ricky>// - **[ed3c65caf](facebook/react@ed3c65caf )**: Warn if outdated JSX transform is detected ([#28781](facebook/react#28781)) //<Andrew Clark>// - **[3f9e237a2](facebook/react@3f9e237a2 )**: Fix: Suspend while recovering from hydration error ([#28800](facebook/react#28800)) //<Andrew Clark>// - **[7f5d25e23](facebook/react@7f5d25e23 )**: Fix cloneElement using string ref w no owner ([#28797](facebook/react#28797)) //<Joseph Savona>// - **[bf40b0244](facebook/react@bf40b0244 )**: [Fizz] Stop publishing external-runtime to stable channel ([#28796](facebook/react#28796)) //<Josh Story>// - **[7f93cb41c](facebook/react@7f93cb41c )**: [DOM] Infer react-server entries bundles if not explicitly configured ([#28795](facebook/react#28795)) //<Josh Story>// - **[f61316535](facebook/react@f61316535 )**: Rename SECRET INTERNALS to `__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE` ([#28789](facebook/react#28789)) //<Sebastian Markbåge>// - **[9644d206e](facebook/react@9644d206e )**: Soften useFormState warning ([#28788](facebook/react#28788)) //<Ricky>// - **[c771016e1](facebook/react@c771016e1 )**: Rename The Secret Export of Server Internals ([#28786](facebook/react#28786)) //<Sebastian Markbåge>// - **[d50323eb8](facebook/react@d50323eb8 )**: Flatten ReactSharedInternals ([#28783](facebook/react#28783)) //<Sebastian Markbåge>// - **[f62cf8c62](facebook/react@f62cf8c62 )**: [Float] treat `props.async` in Float consistent with the rest of react-dom ([#26760](facebook/react#26760)) //<Josh Story>// - **[dfd3d5af8](facebook/react@dfd3d5af8 )**: Add support for transition{run,start,cancel} events ([#27345](facebook/react#27345)) //<Hugo Sales>// - **[1f8327f83](facebook/react@1f8327f83 )**: [Fiber] Use real event priority for hydration scheduling ([#28765](facebook/react#28765)) //<Josh Story>// - **[97c90ed88](facebook/react@97c90ed88 )**: [DOM] Shrink ReactDOMCurrentDispatcher method names ([#28770](facebook/react#28770)) //<Josh Story>// - **[9007fdc8f](facebook/react@9007fdc8f )**: [DOM] Shrink ReactDOMSharedInternals source representation ([#28771](facebook/react#28771)) //<Josh Story>// - **[14f50ad15](facebook/react@14f50ad15 )**: [Flight] Allow lazily resolving outlined models ([#28780](facebook/react#28780)) //<Sebastian Markbåge>// - **[4c12339ce](facebook/react@4c12339ce )**: [DOM] move `flushSync` out of the reconciler ([#28500](facebook/react#28500)) //<Josh Story>// - **[8e1462e8c](facebook/react@8e1462e8c )**: [Fiber] Move updatePriority tracking to renderers ([#28751](facebook/react#28751)) //<Josh Story>// - **[0b3b8a6a3](facebook/react@0b3b8a6a3 )**: jsx: Remove unnecessary hasOwnProperty check ([#28775](facebook/react#28775)) //<Andrew Clark>// - **[2acfb7b60](facebook/react@2acfb7b60 )**: [Flight] Support FormData from Server to Client ([#28754](facebook/react#28754)) //<Sebastian Markbåge>// - **[d1547defe](facebook/react@d1547defe )**: Fast JSX: Don't clone props object ([#28768](facebook/react#28768)) //<Andrew Clark>// - **[bfd8da807](facebook/react@bfd8da807 )**: Make class prop resolution faster ([#28766](facebook/react#28766)) //<Andrew Clark>// - **[cbb6f2b54](facebook/react@cbb6f2b54 )**: [Flight] Support Blobs from Server to Client ([#28755](facebook/react#28755)) //<Sebastian Markbåge>// - **[f33a6b69c](facebook/react@f33a6b69c )**: Track Owner for Server Components in DEV ([#28753](facebook/react#28753)) //<Sebastian Markbåge>// - **[e3ebcd54b](facebook/react@e3ebcd54b )**: Move string ref coercion to JSX runtime ([#28473](facebook/react#28473)) //<Andrew Clark>// - **[fd0da3eef](facebook/react@fd0da3eef )**: Remove _owner field from JSX elements in prod if string refs are disabled ([#28739](facebook/react#28739)) //<Sebastian Markbåge>// Changelog: [General][Changed] - React Native sync for revisions 48b4ecc...b5e5ce8 jest_e2e[run_all_tests] bypass-github-export-checks Reviewed By: kassens Differential Revision: D56251607 fbshipit-source-id: e16db2fa101fc7ed1e009158c76388206beabd5f
) For [`AsyncIterable`](#28847) we encode `AsyncIterator` as a separate tag. Previously we encoded `Iterator` as just an Array. This adds a special encoding for this. Technically this is a breaking change. This is kind of an edge case that you'd care about the difference but it becomes more important to treat these correctly for the warnings here #28853.
Same as #28847 but in the other direction. Like other promises, this doesn't actually stream in the outgoing direction. It buffers until the stream is done. This is mainly due to our protocol remains compatible with Safari's lack of outgoing streams until recently. However, the stream chunks are encoded as separate fields and so does support the busboy streaming on the receiving side.
This adds support in Flight for serializing four kinds of streams:
ReadableStream
with objects as a model. This is a single shot iterator so you can read it only once. It can contain any value including Server Components. Chunks are encoded as is so if you send in 10 typed arrays, you get the same typed arrays out on the other side.ReadableStream
withtype: 'bytes'
option. This supports the BYOB protocol. In this mode, the receiving side just getsUint8Array
s and they can be split across any single byte boundary into arbitrary chunks.AsyncIterable
where theAsyncIterator
function is different than theAsyncIterable
itself. In this case we assume that this might be a multi-shot iterable and so we buffer its value and you can iterate it multiple times on the other side. We support thereturn
value as a value in the single completion slot, but you can't pass values innext()
. If you want single-shot, return the AsyncIterator instead.AsyncIterator
. These gets serialized as a single-shot as it's just an iterator.AsyncIterable
/AsyncIterator
yield Promises that are instrumented with our.status
/.value
convention so that they can be synchronously looped over if available. They are also lazily parsed upon read.We can't do this with
ReadableStream
because we use the native implementation ofReadableStream
which owns the promises.The format is a leading row that indicates which type of stream it is. Then a new row with the same ID is emitted for every chunk. Followed by either an error or close row.
AsyncIterable
s can also be returned as children of Server Components and then they're conceptually the same as fragment arrays/iterables. They can't actually be used as children in Fizz/Fiber but there's a separate plan for that. OnlyAsyncIterable
notAsyncIterator
will be valid as children - just like syncIterable
is already supported but single-shotIterator
is not. Notably, neither of these streams represent updates over time to a value. They represent multiple values in a list.When the server stream is aborted we also close the underlying stream. However, closing a stream on the client, doesn't close the underlying stream.
A couple of possible follow ups I'm not planning on doing right now: