Files
react/packages/react-devtools-shared/src/devtools/cache.js
T
Jan Kassens 68adb0b4ea Flow: inference_mode=constrain_writes
This mode is going to be the new default in Flow going forward.
There was an unfortuante large number of suppressions in this update.

More on the changes can be found in this [Flow blog post](https://medium.com/flow-type/new-flow-language-rule-constrained-writes-4c70e375d190).

Added some of the required annotations using the provided codemod:

```sh
node_modules/.bin/flow codemod annotate-declarations --write .
```

ghstack-source-id: 0b168e1b23
Pull Request resolved: https://github.com/facebook/react/pull/25422
2022-10-04 15:49:48 -04:00

209 lines
5.3 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
*/
import type {Thenable} from 'shared/ReactTypes';
import * as React from 'react';
import {createContext} from 'react';
// TODO (cache) Remove this cache; it is outdated and will not work with newer APIs like startTransition.
// Cache implementation was forked from the React repo:
// https://github.com/facebook/react/blob/main/packages/react-cache/src/ReactCache.js
//
// This cache is simpler than react-cache in that:
// 1. Individual items don't need to be invalidated.
// Profiling data is invalidated as a whole.
// 2. We didn't need the added overhead of an LRU cache.
// The size of this cache is bounded by how many renders were profiled,
// and it will be fully reset between profiling sessions.
export type {Thenable};
interface Suspender {
then(resolve: () => mixed, reject: () => mixed): mixed;
}
type PendingResult = {
status: 0,
value: Suspender,
};
type ResolvedResult<Value> = {
status: 1,
value: Value,
};
type RejectedResult = {
status: 2,
value: mixed,
};
type Result<Value> = PendingResult | ResolvedResult<Value> | RejectedResult;
export type Resource<Input, Key, Value> = {
clear(): void,
invalidate(Key): void,
read(Input): Value,
preload(Input): void,
write(Key, Value): void,
...
};
const Pending = 0;
const Resolved = 1;
const Rejected = 2;
const ReactCurrentDispatcher = (React: any)
.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher;
function readContext(Context) {
const dispatcher = ReactCurrentDispatcher.current;
if (dispatcher === null) {
throw new Error(
'react-cache: read and preload may only be called from within a ' +
"component's render. They are not supported in event handlers or " +
'lifecycle methods.',
);
}
return dispatcher.readContext(Context);
}
const CacheContext = createContext(null);
type Config = {useWeakMap?: boolean, ...};
const entries: Map<
Resource<any, any, any>,
Map<any, any> | WeakMap<any, any>,
> = new Map();
const resourceConfigs: Map<Resource<any, any, any>, Config> = new Map();
function getEntriesForResource(
resource: any,
): Map<any, any> | WeakMap<any, any> {
let entriesForResource: Map<any, any> | WeakMap<any, any> = ((entries.get(
resource,
): any): Map<any, any>);
if (entriesForResource === undefined) {
const config = resourceConfigs.get(resource);
entriesForResource =
config !== undefined && config.useWeakMap ? new WeakMap() : new Map();
entries.set(resource, entriesForResource);
}
return entriesForResource;
}
function accessResult<Input, Key, Value>(
resource: any,
fetch: Input => Thenable<Value>,
input: Input,
key: Key,
): Result<Value> {
const entriesForResource = getEntriesForResource(resource);
const entry = entriesForResource.get(key);
if (entry === undefined) {
const thenable = fetch(input);
thenable.then(
value => {
if (newResult.status === Pending) {
const resolvedResult: ResolvedResult<Value> = (newResult: any);
resolvedResult.status = Resolved;
resolvedResult.value = value;
}
},
error => {
if (newResult.status === Pending) {
const rejectedResult: RejectedResult = (newResult: any);
rejectedResult.status = Rejected;
rejectedResult.value = error;
}
},
);
const newResult: PendingResult = {
status: Pending,
// $FlowFixMe[method-unbinding]
value: thenable,
};
entriesForResource.set(key, newResult);
return newResult;
} else {
return entry;
}
}
export function createResource<Input, Key, Value>(
fetch: Input => Thenable<Value>,
hashInput: Input => Key,
config?: Config = {},
): Resource<Input, Key, Value> {
const resource = {
clear(): void {
entries.delete(resource);
},
invalidate(key: Key): void {
const entriesForResource = getEntriesForResource(resource);
entriesForResource.delete(key);
},
read(input: Input): Value {
// Prevent access outside of render.
readContext(CacheContext);
const key = hashInput(input);
const result: Result<Value> = accessResult(resource, fetch, input, key);
switch (result.status) {
case Pending: {
const suspender = result.value;
throw suspender;
}
case Resolved: {
const value = result.value;
return value;
}
case Rejected: {
const error = result.value;
throw error;
}
default:
// Should be unreachable
return (undefined: any);
}
},
preload(input: Input): void {
// Prevent access outside of render.
readContext(CacheContext);
const key = hashInput(input);
accessResult(resource, fetch, input, key);
},
write(key: Key, value: Value): void {
const entriesForResource = getEntriesForResource(resource);
const resolvedResult: ResolvedResult<Value> = {
status: Resolved,
value,
};
entriesForResource.set(key, resolvedResult);
},
};
resourceConfigs.set(resource, config);
return resource;
}
export function invalidateResources(): void {
entries.clear();
}