[ghstack-poisoned]
This commit is contained in:
Mofei Zhang
2024-10-02 16:18:33 -04:00
8 changed files with 114 additions and 28 deletions
+16 -1
View File
@@ -185,7 +185,7 @@ export function processReply(
temporaryReferences: void | TemporaryReferenceSet,
resolve: (string | FormData) => void,
reject: (error: mixed) => void,
): void {
): (reason: mixed) => void {
let nextPartId = 1;
let pendingParts = 0;
let formData: null | FormData = null;
@@ -841,6 +841,19 @@ export function processReply(
return JSON.stringify(model, resolveToJSON);
}
function abort(reason: mixed): void {
if (pendingParts > 0) {
pendingParts = 0; // Don't resolve again later.
// Resolve with what we have so far, which may have holes at this point.
// They'll error when the stream completes on the server.
if (formData === null) {
resolve(json);
} else {
resolve(formData);
}
}
}
const json = serializeModel(root, 0);
if (formData === null) {
@@ -854,6 +867,8 @@ export function processReply(
resolve(formData);
}
}
return abort;
}
const boundCache: WeakMap<
+12 -17
View File
@@ -112,31 +112,32 @@ export interface INativeMethods {
measure(callback: MeasureOnSuccessCallback): void;
measureInWindow(callback: MeasureInWindowOnSuccessCallback): void;
measureLayout(
relativeToNativeNode: number | ElementRef<HostComponent<mixed>>,
relativeToNativeNode: number | HostInstance,
onSuccess: MeasureLayoutOnSuccessCallback,
onFail?: () => void,
): void;
setNativeProps(nativeProps: {...}): void;
}
export type NativeMethods = $ReadOnly<{|
export type NativeMethods = $ReadOnly<{
blur(): void,
focus(): void,
measure(callback: MeasureOnSuccessCallback): void,
measureInWindow(callback: MeasureInWindowOnSuccessCallback): void,
measureLayout(
relativeToNativeNode: number | ElementRef<HostComponent<mixed>>,
relativeToNativeNode: number | HostInstance,
onSuccess: MeasureLayoutOnSuccessCallback,
onFail?: () => void,
): void,
setNativeProps(nativeProps: {...}): void,
|}>;
}>;
// This validates that INativeMethods and NativeMethods stay in sync using Flow!
declare const ensureNativeMethodsAreSynced: NativeMethods;
(ensureNativeMethodsAreSynced: INativeMethods);
export type HostComponent<T> = AbstractComponent<T, $ReadOnly<NativeMethods>>;
export type HostInstance = NativeMethods;
export type HostComponent<Config> = AbstractComponent<Config, HostInstance>;
type SecretInternalsType = {
computeComponentStackForErrorReporting(tag: number): string,
@@ -209,7 +210,7 @@ export type RenderRootOptions = {
export type ReactNativeType = {
findHostInstance_DEPRECATED<TElementType: ElementType>(
componentOrHandle: ?(ElementRef<TElementType> | number),
): ?ElementRef<HostComponent<mixed>>,
): ?HostInstance,
findNodeHandle<TElementType: ElementType>(
componentOrHandle: ?(ElementRef<TElementType> | number),
): ?number,
@@ -218,14 +219,11 @@ export type ReactNativeType = {
child: PublicInstance | HostComponent<mixed>,
): boolean,
dispatchCommand(
handle: ElementRef<HostComponent<mixed>>,
handle: HostInstance,
command: string,
args: Array<mixed>,
): void,
sendAccessibilityEvent(
handle: ElementRef<HostComponent<mixed>>,
eventType: string,
): void,
sendAccessibilityEvent(handle: HostInstance, eventType: string): void,
render(
element: MixedElement,
containerTag: number,
@@ -247,20 +245,17 @@ type PublicTextInstance = mixed;
export type ReactFabricType = {
findHostInstance_DEPRECATED<TElementType: ElementType>(
componentOrHandle: ?(ElementRef<TElementType> | number),
): ?ElementRef<HostComponent<mixed>>,
): ?HostInstance,
findNodeHandle<TElementType: ElementType>(
componentOrHandle: ?(ElementRef<TElementType> | number),
): ?number,
dispatchCommand(
handle: ElementRef<HostComponent<mixed>>,
handle: HostInstance,
command: string,
args: Array<mixed>,
): void,
isChildPublicInstance(parent: PublicInstance, child: PublicInstance): boolean,
sendAccessibilityEvent(
handle: ElementRef<HostComponent<mixed>>,
eventType: string,
): void,
sendAccessibilityEvent(handle: HostInstance, eventType: string): void,
render(
element: MixedElement,
containerTag: number,
@@ -121,12 +121,12 @@ function createFromFetch<T>(
function encodeReply(
value: ReactServerValue,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {temporaryReferences?: TemporaryReferenceSet, signal?: AbortSignal},
): Promise<
string | URLSearchParams | FormData,
> /* We don't use URLSearchParams yet but maybe */ {
return new Promise((resolve, reject) => {
processReply(
const abort = processReply(
value,
'',
options && options.temporaryReferences
@@ -135,6 +135,18 @@ function encodeReply(
resolve,
reject,
);
if (options && options.signal) {
const signal = options.signal;
if (signal.aborted) {
abort((signal: any).reason);
} else {
const listener = () => {
abort((signal: any).reason);
signal.removeEventListener('abort', listener);
};
signal.addEventListener('abort', listener);
}
}
});
}
@@ -120,12 +120,12 @@ function createFromFetch<T>(
function encodeReply(
value: ReactServerValue,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {temporaryReferences?: TemporaryReferenceSet, signal?: AbortSignal},
): Promise<
string | URLSearchParams | FormData,
> /* We don't use URLSearchParams yet but maybe */ {
return new Promise((resolve, reject) => {
processReply(
const abort = processReply(
value,
'',
options && options.temporaryReferences
@@ -134,6 +134,18 @@ function encodeReply(
resolve,
reject,
);
if (options && options.signal) {
const signal = options.signal;
if (signal.aborted) {
abort((signal: any).reason);
} else {
const listener = () => {
abort((signal: any).reason);
signal.removeEventListener('abort', listener);
};
signal.addEventListener('abort', listener);
}
}
});
}
@@ -149,12 +149,12 @@ function createFromFetch<T>(
function encodeReply(
value: ReactServerValue,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {temporaryReferences?: TemporaryReferenceSet, signal?: AbortSignal},
): Promise<
string | URLSearchParams | FormData,
> /* We don't use URLSearchParams yet but maybe */ {
return new Promise((resolve, reject) => {
processReply(
const abort = processReply(
value,
'',
options && options.temporaryReferences
@@ -163,6 +163,18 @@ function encodeReply(
resolve,
reject,
);
if (options && options.signal) {
const signal = options.signal;
if (signal.aborted) {
abort((signal: any).reason);
} else {
const listener = () => {
abort((signal: any).reason);
signal.removeEventListener('abort', listener);
};
signal.addEventListener('abort', listener);
}
}
});
}
@@ -618,4 +618,20 @@ describe('ReactFlightDOMReply', () => {
const root = await ReactServerDOMServer.decodeReply(body, webpackServerMap);
expect(root.prop.obj).toBe(root.prop);
});
it('can abort an unresolved model and get the partial result', async () => {
const promise = new Promise(r => {});
const controller = new AbortController();
const bodyPromise = ReactServerDOMClient.encodeReply(
{promise: promise, hello: 'world'},
{signal: controller.signal},
);
controller.abort();
const result = await ReactServerDOMServer.decodeReply(await bodyPromise);
expect(result.hello).toBe('world');
// TODO: await result.promise should reject at this point because the stream
// has closed but that's a bug in both ReactFlightReplyServer and ReactFlightClient.
// It just halts in this case.
});
});
@@ -120,12 +120,12 @@ function createFromFetch<T>(
function encodeReply(
value: ReactServerValue,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {temporaryReferences?: TemporaryReferenceSet, signal?: AbortSignal},
): Promise<
string | URLSearchParams | FormData,
> /* We don't use URLSearchParams yet but maybe */ {
return new Promise((resolve, reject) => {
processReply(
const abort = processReply(
value,
'',
options && options.temporaryReferences
@@ -134,6 +134,18 @@ function encodeReply(
resolve,
reject,
);
if (options && options.signal) {
const signal = options.signal;
if (signal.aborted) {
abort((signal: any).reason);
} else {
const listener = () => {
abort((signal: any).reason);
signal.removeEventListener('abort', listener);
};
signal.addEventListener('abort', listener);
}
}
});
}
@@ -149,12 +149,12 @@ function createFromFetch<T>(
function encodeReply(
value: ReactServerValue,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {temporaryReferences?: TemporaryReferenceSet, signal?: AbortSignal},
): Promise<
string | URLSearchParams | FormData,
> /* We don't use URLSearchParams yet but maybe */ {
return new Promise((resolve, reject) => {
processReply(
const abort = processReply(
value,
'',
options && options.temporaryReferences
@@ -163,6 +163,18 @@ function encodeReply(
resolve,
reject,
);
if (options && options.signal) {
const signal = options.signal;
if (signal.aborted) {
abort((signal: any).reason);
} else {
const listener = () => {
abort((signal: any).reason);
signal.removeEventListener('abort', listener);
};
signal.addEventListener('abort', listener);
}
}
});
}