mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
* [Flight] Align Chunks with Thenable used with experimental_use Use the field names used by the Thenable data structure passed to use(). These are considered public in this model. This adds another field since we use a separate field name for "reason". * Implement Thenable Protocol on Chunks This doesn't just ping but resolves/rejects with the value. * Subclass Promises * Pass key through JSON parsing * Wait for preloadModules before resolving module chunks * Initialize lazy resolved values before reading the result * Block a model from initializing if its direct dependencies are pending If a module is blocked, then we can't complete initializing a model. However, we can still let it parse, and then fill in the missing pieces later. We need to block it from resolving until all dependencies have filled in which we can do with a ref count. * Treat blocked modules or models as a special status We currently loop over all chunks at the end to error them if they're still pending. We shouldn't do this if they're pending because they're blocked on an external resource like a module because the module might not resolve before the Flight connection closes and that's not an error. In an alternative solution I had a set that tracked pending chunks and removed one at a time. While the loop at the end is faster it's more work as we go. I figured the extra status might also help debugging. For modules we can probably assume no forward references, and the first async module we can just use the promise as the chunk. So we could probably get away with this only on models that are blocked by modules.
107 lines
3.5 KiB
JavaScript
107 lines
3.5 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
// Corresponds to ReactFiberWakeable and ReactFlightWakeable modules. Generally,
|
|
// changes to one module should be reflected in the others.
|
|
|
|
// TODO: Rename this module and the corresponding Fiber one to "Thenable"
|
|
// instead of "Wakeable". Or some other more appropriate name.
|
|
|
|
import type {
|
|
Wakeable,
|
|
Thenable,
|
|
PendingThenable,
|
|
FulfilledThenable,
|
|
RejectedThenable,
|
|
} from 'shared/ReactTypes';
|
|
|
|
// TODO: Sparse arrays are bad for performance.
|
|
export opaque type ThenableState = Array<Thenable<any> | void>;
|
|
|
|
export function createThenableState(): ThenableState {
|
|
// The ThenableState is created the first time a component suspends. If it
|
|
// suspends again, we'll reuse the same state.
|
|
return [];
|
|
}
|
|
|
|
export function trackSuspendedWakeable(wakeable: Wakeable) {
|
|
// If this wakeable isn't already a thenable, turn it into one now. Then,
|
|
// when we resume the work loop, we can check if its status is
|
|
// still pending.
|
|
// TODO: Get rid of the Wakeable type? It's superseded by UntrackedThenable.
|
|
const thenable: Thenable<mixed> = (wakeable: any);
|
|
|
|
// We use an expando to track the status and result of a thenable so that we
|
|
// can synchronously unwrap the value. Think of this as an extension of the
|
|
// Promise API, or a custom interface that is a superset of Thenable.
|
|
//
|
|
// If the thenable doesn't have a status, set it to "pending" and attach
|
|
// a listener that will update its status and result when it resolves.
|
|
switch (thenable.status) {
|
|
case 'fulfilled':
|
|
case 'rejected':
|
|
// A thenable that already resolved shouldn't have been thrown, so this is
|
|
// unexpected. Suggests a mistake in a userspace data library. Don't track
|
|
// this thenable, because if we keep trying it will likely infinite loop
|
|
// without ever resolving.
|
|
// TODO: Log a warning?
|
|
break;
|
|
default: {
|
|
if (typeof thenable.status === 'string') {
|
|
// Only instrument the thenable if the status if not defined. If
|
|
// it's defined, but an unknown value, assume it's been instrumented by
|
|
// some custom userspace implementation. We treat it as "pending".
|
|
break;
|
|
}
|
|
const pendingThenable: PendingThenable<mixed> = (thenable: any);
|
|
pendingThenable.status = 'pending';
|
|
pendingThenable.then(
|
|
fulfilledValue => {
|
|
if (thenable.status === 'pending') {
|
|
const fulfilledThenable: FulfilledThenable<mixed> = (thenable: any);
|
|
fulfilledThenable.status = 'fulfilled';
|
|
fulfilledThenable.value = fulfilledValue;
|
|
}
|
|
},
|
|
(error: mixed) => {
|
|
if (thenable.status === 'pending') {
|
|
const rejectedThenable: RejectedThenable<mixed> = (thenable: any);
|
|
rejectedThenable.status = 'rejected';
|
|
rejectedThenable.reason = error;
|
|
}
|
|
},
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
export function trackUsedThenable<T>(
|
|
thenableState: ThenableState,
|
|
thenable: Thenable<T>,
|
|
index: number,
|
|
) {
|
|
// This is only a separate function from trackSuspendedWakeable for symmetry
|
|
// with Fiber.
|
|
thenableState[index] = thenable;
|
|
}
|
|
|
|
export function getPreviouslyUsedThenableAtIndex<T>(
|
|
thenableState: ThenableState | null,
|
|
index: number,
|
|
): Thenable<T> | null {
|
|
if (thenableState !== null) {
|
|
const thenable = thenableState[index];
|
|
if (thenable !== undefined) {
|
|
return thenable;
|
|
}
|
|
}
|
|
return null;
|
|
}
|