/**
* Copyright (c) Meta Platforms, Inc. and 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 invokeGuardedCallbackImpl from './invokeGuardedCallbackImpl';
// Used by Fiber to simulate a try-catch.
let hasError: boolean = false;
let caughtError: mixed = null;
// Used by event system to capture/rethrow the first error.
let hasRethrowError: boolean = false;
let rethrowError: mixed = null;
const reporter = {
onError(error: mixed) {
hasError = true;
caughtError = error;
},
};
/**
* Call a function while guarding against errors that happens within it.
* Returns an error if it throws, otherwise null.
*
* In production, this is implemented using a try-catch. The reason we don't
* use a try-catch directly is so that we can swap out a different
* implementation in DEV mode.
*
* @param {String} name of the guard to use for logging or debugging
* @param {Function} func The function to invoke
* @param {*} context The context to use when calling the function
* @param {...*} args Arguments for function
*/
export function invokeGuardedCallback<A, B, C, D, E, F, Context>(
name: string | null,
func: (a: A, b: B, c: C, d: D, e: E, f: F) => mixed,
context: Context,
a: A,
b: B,
c: C,
d: D,
e: E,
f: F,
): void {
hasError = false;
caughtError = null;
invokeGuardedCallbackImpl.apply(reporter, arguments);
}
/**
* Same as invokeGuardedCallback, but instead of returning an error, it stores
* it in a global so it can be rethrown by `rethrowCaughtError` later.
* TODO: See if caughtError and rethrowError can be unified.
*
* @param {String} name of the guard to use for logging or debugging
* @param {Function} func The function to invoke
* @param {*} context The context to use when calling the function
* @param {...*} args Arguments for function
*/
export function invokeGuardedCallbackAndCatchFirstError<
A,
B,
C,
D,
E,
F,
Context,
>(
this: mixed,
name: string | null,
func: (a: A, b: B, c: C, d: D, e: E, f: F) => void,
context: Context,
a: A,
b: B,
c: C,
d: D,
e: E,
f: F,
): void {
invokeGuardedCallback.apply(this, arguments);
if (hasError) {
const error = clearCaughtError();
if (!hasRethrowError) {
hasRethrowError = true;
rethrowError = error;
}
}
}
/**
* During execution of guarded functions we will capture the first error which
* we will rethrow to be handled by the top level error handler.
*/
export function rethrowCaughtError() {
if (hasRethrowError) {
const error = rethrowError;
hasRethrowError = false;
rethrowError = null;
throw error;
}
}
export function hasCaughtError(): boolean {
return hasError;
}
export function clearCaughtError(): mixed {
if (hasError) {
const error = caughtError;
hasError = false;
caughtError = null;
return error;
} else {
throw new Error(
'clearCaughtError was called but no error was captured. This error ' +
'is likely caused by a bug in React. Please file an issue.',
);
}
}