DEV Community

Na'aman Hirschfeld
Na'aman Hirschfeld

Posted on

Functional Snippet: withSuppress

Sometimes, suppressing errors is handy.

E.g. you see this:

try {
  scrollableRef.update()
} catch {}
Enter fullscreen mode Exit fullscreen mode

Let's create something to handle this in a cleaner fashion. We'll use a functional style that prefers wrap functions to extend functionality.

We "wrap" a function with error handling, returning a function with an identical signature:

import { isPromise } from '@tool-belt/type-predicates';

/**
 * Wraps a function with error suppression and optional logging.
 *
 * @param fn - The function to execute.
 * @param errorMessage - The error message to log if an exception is thrown.
 * @returns The wrapped function with error suppression.
 */
export function withSuppress<T extends (...args: any[]) => any>(
  fn: T,
  errorMessage?: string,
): (...args: Parameters<T>) => ReturnType<T> extends Promise<infer U>
  ? Promise<U | undefined>
  : ReturnType<T> | undefined {
  const log = errorMessage ? (msg: string) => console.error(msg) : null;

  return (...args: Parameters<T>) => {
    try {
      const result = fn(...args);

      if (isPromise(result)) {
        return result.catch((error) => {
          log?.(`${errorMessage}\n${error?.stack ?? error}`);
          return undefined;
        }) as ReturnType<T> extends Promise<infer U> ? Promise<U | undefined> : never;
      }

      return result;
    } catch (error) {
      log?.(`${errorMessage}\n${error?.stack ?? error}`);
      return undefined;
    }
  };
}
Enter fullscreen mode Exit fullscreen mode

And now we can suppress errors elegantly.

Here are some example uses:

# generate content
Enter fullscreen mode Exit fullscreen mode

Here is a Vitest test suite for the function:

import { withSuppress } from './functional';

describe('withSuppress', () => {
  it('should return the value from a synchronous function without errors', () => {
    const syncFn = () => 42;
    const wrappedFn = withSuppress(syncFn);
    expect(wrappedFn()).toBe(42);
  });

  it('should return undefined and log error when synchronous function throws', () => {
    const errorMessage = 'Sync error occurred';
    const syncFn = () => {
      throw new Error('Test Error');
    };
    const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});

    const wrappedFn = withSuppress(syncFn, errorMessage);
    expect(wrappedFn()).toBeUndefined();
    expect(consoleErrorSpy).toHaveBeenCalledWith(
      expect.stringContaining(`${errorMessage}\nError: Test Error`),
    );

    consoleErrorSpy.mockRestore();
  });

  it('should return the resolved value from an asynchronous function without errors', async () => {
    const asyncFn = async () => await Promise.resolve(42);
    const wrappedFn = withSuppress(asyncFn);
    await expect(wrappedFn()).resolves.toBe(42);
  });

  it('should return undefined and log error when asynchronous function rejects', async () => {
    const errorMessage = 'Async error occurred';
    const asyncFn = async () => await Promise.reject(new Error('Test Error'));
    const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});

    const wrappedFn = withSuppress(asyncFn, errorMessage);
    await expect(wrappedFn()).resolves.toBeUndefined();
    expect(consoleErrorSpy).toHaveBeenCalledWith(
      expect.stringContaining(`${errorMessage}\nError: Test Error`),
    );

    consoleErrorSpy.mockRestore();
  });

  it('should return undefined if no error message is provided and the function throws', () => {
    const syncFn = () => {
      throw new Error('Test Error');
    };
    const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});

    const wrappedFn = withSuppress(syncFn);
    expect(wrappedFn()).toBeUndefined();
    expect(consoleErrorSpy).not.toHaveBeenCalled();

    consoleErrorSpy.mockRestore();
  });

  it('should return undefined if no error message is provided and the async function rejects', async () => {
    const asyncFn = async () => await Promise.reject(new Error('Test Error'));
    const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});

    const wrappedFn = withSuppress(asyncFn);
    await expect(wrappedFn()).resolves.toBeUndefined();
    expect(consoleErrorSpy).not.toHaveBeenCalled();

    consoleErrorSpy.mockRestore();
  });

  it('should handle functions that return promises without errors', async () => {
    const asyncFn = async () => await Promise.resolve('async result');
    const wrappedFn = withSuppress(asyncFn);
    await expect(wrappedFn()).resolves.toBe('async result');
  });

  it('should handle synchronous functions returning undefined without errors', () => {
    const syncFn = () => undefined;
    const wrappedFn = withSuppress(syncFn);
    expect(wrappedFn()).toBeUndefined();
  });
});
Enter fullscreen mode Exit fullscreen mode

Top comments (0)