React Internals do some Weird Shit™
I really enjoy seeing all the different ways a programming language can be used in unexpected ways, and the JavaScript community has historically been very good at pushing the language to its limits, despite some deficiencies. I wanted to jot down some of the JS tricks I've seen in use within the React source. Like them or not, they're probably in your bundle, so it's worth knowing the how and why.
Throwing Promises
Note: The React Docs are very clear that this API is "unstable and undocumented"
React's "Suspense" feature enables data fetching libraries to interrupt ("suspend") rendering and signal to a component that it needs to pause to fetch data. The API exposed to consumers of Suspense-enabled libraries has an interesting feature: it almost looks invisible.
function SomeComponentFetchingData({ id }) {
const data = readFromSuspenseEnabledDataSource(id);
return <div>{data.someprop}</div>;
}
Although it doesn't stand out reading the code, the readFromSuspenseEnabledDataSource
function signals to React that SomeComponentFetchingData
needs to Suspend, and it prevents the next line of code (the access to data.someprop
) from executing until a Promise resolves. The readFromSuspenseEnabledDataSource
function does this by throwing a Promise synchronously:
// pseudo-code, does not fulfill the full contract needed by React, and doesn't include error handling.
function readFromSuspenseEnabledDataSource(id) {
if (someCache.has(id)) {
// synchronously return value if already fetched
return someCache.get(id);
}
if (pendingRequests.has(id)) {
// if request for this data is in-flight,
// throw same Promise as we did before
throw promise;
}
// trigger data fetch
const promise = fetchSomeResource(id);
promise.then(res => {
// remove promise from pending list
// so we know it has completed
pendingRequests.remove(id);
// record result in our cache so we can
// return result synchronously later
someCache.set(id, res);
});
// throw promise to signal we're suspending
throw promise;
}
Throwing promises works because of a fun little quirk in JavaScript: the throw
statement takes any value, not just an Error object.
try {
throw 'lol';
} catch (err) {
console.log(typeof err); // "string"
}
It feels gross, but I'm really impressed with the general idea of using throw
to allow any function in a component's callstack to yield some value. It's probably not super useful as a pattern outside of React, though, as there's quite a few constraints placed on React components that made this design feasible (mainly the explicit management of state and effects).
Monkey-patching Global Fetch
Note: Gated by enableCache
and enableFetchInstrumentation
feature flags
The in progress caching APIs (to go along with Suspense) have some form of automatic HTTP request caching. This is done by overwriting the global copy of fetch
with a new wrapper
try {
// eslint-disable-next-line no-native-reassign
fetch = cachedFetch;
} catch (error1) {
try {
// In case assigning it globally fails, try globalThis instead just in case it exists.
globalThis.fetch = cachedFetch;
} catch (error2) {
// Log even in production just to make sure this is seen if only prod is frozen.
// eslint-disable-next-line react-internal/no-production-logging
console.warn(
'React was unable to patch the fetch() function in this environment. ' +
'Suspensey APIs might not work correctly as a result.',
);
}
}
The original PR explains some of the motivation
this gives deduping at the network layer to avoid costly mistakes and to make the simple case simple.
I can see the reasoning for most of the tricks used in this post, but the monkey patching of fetch
feels out of place. Unless I missed something else, it doesn't seem hugely beneficial compared to React exporting a fetch
wrapper.
It is worth noting that this function is mostly a proxy to the native fetch
, as it checks whether the call to fetch is happening within a React component context:
const dispatcher = ReactCurrentCache.current;
if (!dispatcher) {
// We're outside a cached scope.
return originalFetch(resource, options);
}
Expando Properties on Promises
In web development1, I believe the term "expando" was first introduced via the expando
property in Internet Explorer's JScript. Over time, I've seen it expand its definition to something like "adding a non-standard property to some host object or built-in."
The new use()
hook works by adding a few expando properties (status
, value
, and reason
) to any Promise passed to it. These properties allow React to synchronously inspect the result of a Promise, which is not possible with standard JavaScript Promises.
The RFC First Class Support for Promises makes it clear that the React team would prefer a native JavaScript API
Keep in mind that React will not add these extra fields to every promise, only those promise objects that are passed to use. It does not require modifying the global environment or the Promise prototype, and will not affect non-React code.
Although this convention is not part of the JavaScript specification, we think it's a reasonable way to track a promise's result. The ideal is that the lifetime of the resolved value corresponds to the lifetime of the promise object. The most straightforward way to implement this is by adding a property directly to the promise.
An alternative would be to use a WeakMap, which offers similar benefits. The advantage of using a property instead of a WeakMap is that other frameworks besides React can access these fields, too. For example, a data framework can set the status and value fields on a promise preemptively, before passing to React, so that React can unwrap it without waiting a microtask.
If JavaScript were to ever adopt a standard API for synchronously inspecting the value of a promise, we would switch to that instead. (Indeed, if an API like Promise.inspect existed, this RFC would be significantly shorter.)
Custom Directive Pragmas
A "Directive Pragma" is a feature of JavaScript that gives special meaning to a string literal at the top of a Script, Module, or Function body. The 1 well-known Pragma that most JavaScript developers have seen is to enable Strict mode:
'use strict'; // strict module
function abc() {
'use strict'; // strict function
}
With Server Components and Server Actions, React introduced 2 custom Directive Pragmas:
'use client'; // Mark that a file contains "Client Components"
function handleSomeFormSubmit() {
'use server'; // Mark that a function is a "Server Action"
}
I've written more about this in another post, "use client" and "use server" are standard-ish.
Footnotes
-
Other languages (C#, Groovy) have similarish concepts using the term "expando", but the term has taken on a very specific meaning in JS land ↩