0

I want to create a wrapper function for an existing function in TypeScript.

The wrapper function could start some other process and clean it up after finishing the main ("callback") function passed to the wrapper.

This can be done using approaches like shown here. However, these solutions do not allow me to specify additional options that can be passed to the wrapper itself.

How would I go about doing that?

My starting point was:

export const wrap = async <T>(
  callback: () => T | Promise<T>,
  options?: { foo?: string | undefined },
): Promise<T> => {
  let ret;

  // begin
  if (options.foo) {
    // do something
  }

  try {
    ret = await callback();
  } catch (e) {
    throw e;
  } finally {
    // cleanup
  }

  return ret;
};

This would not let me add arguments to callback(). I can use ...args, but how would I specify both ...args and options?

2 Answers 2

2

I suggest you to write your function as a factory (function that returns another function):

function wrap<T extends (...args: any[]) => any>(callback: T, options?: { foo?: string | undefined }): (...args: Parameters<T>) => ReturnType<T> extends Promise<infer U> ? Promise<U> : Promise<ReturnType<T>>
function wrap(callback: (...args: any[]) => any, options?: { foo?: string | undefined }): (...args: any[]) => Promise<any> {

  return async function (...args: any[]) {
    let ret;

    // begin
    if (options && options.foo) {
      // do something
    }

    try {
      ret = await callback(...args);
    } catch (e) {
      throw e;
    } finally {
      // cleanup
    }

    return ret;
  }
};

async function asyncExtractFirstParameter(str: string, num: number, bool: boolean) {
  return str;
}

function extractFirstParameter(str: string, num: number, bool: boolean) {
  return str;
}

const wrappedAsyncExtractFirstParameter = wrap(asyncExtractFirstParameter);
const wrappedExtractFirstParameter = wrap(extractFirstParameter);

wrappedAsyncExtractFirstParameter('test', 23, true).then(console.log);
wrappedExtractFirstParameter('test', 23, true).then(console.log);

That allows you to create another function with the same signature as the callback, and possibly defer its execution.

TypeScript playground

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks, that is nice. I don't necessarily need to defer here, but this is more generally usable.
Coming back to this, can I somehow type the ...args: any[] so that instead it validates the actual parameters of the function passed? I guess I could use ReturnType<T> and Parameters<T> but I am not sure how.
I have updated my answer with a possible solution
1

The solution consists of making the functon generic (F) and inferring the parameters and return types of that function via ReturnType and Parameters. Any additional options can be specified in advance of the destructured remaining arguments which are passed to the callback function:

export const wrap = async <F extends (...args: any[]) => ReturnType<F> | Promise<ReturnType<F>>>(
  callback: F,
  options: { foo?: string | undefined } = {},
  ...args: Parameters<F>
): Promise<ReturnType<F>> => {
  let ret: ReturnType<F> | undefined;

  // begin
  if (options.foo) {
    // do something
  }

  try {
    ret = await callback(...args);
  } catch (e) {
    throw e;
  } finally {
    // cleanup
  }

  return ret;
};

The wrapper can then be called like this:

const someFunction = (arg1: string, arg2: boolean): void => {
  // do something
}

wrap(someFunction, { foo: "yes" }, arg1, arg2)`

All return types and argument types are inferred automatically.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.