Marya
Marya

Reputation: 170

How to define the correct return type in Higher-Order Functions

I'm using Next.js, Auth.js and mongoose, my problem is I wanted to wrap all my controllers with a wrapper/helper function called withDBConnection to save me time not establishing a connection with the db each time, it takes the controller and returns it after connecting:

type Controller <Args extends any[]> = (...args: Args) => any;
export const withDBConnection = <Args extends any[]> (Controller:Controller<Args>):(...args:Args) => Promise<any> => {
    return async (...args: Args) => {
    try {
      await dbStartConnection();
      Controller(...args);
    } catch (error) {
      console.log("inside withDBConnection catch block");
      console.log((error as AppError).message);
    }
  };}

an example of using the helper function:

export const getUser = withDBConnection( async(email: string) => {
  const user = await Auth.find({discordEmail: email});
  console.log("getUser", user);
  return user;
});

but the controller doesn't return the value, which is user, I can't use it in the Auth.js signIn() callback:

const isExist = await getUser(user.email);

isExist is always undefined so the app goes to createNewUser controller:

if(!isExist) await createNewUser(user);

I tried to change the return type from any to mongoose type Document in both type Controller and withDBConnection but that made the ts complain about the email: string

Upvotes: 1

Views: 42

Answers (1)

DarkTone
DarkTone

Reputation: 56

The main issue was that Controller(...args); is called without returning its result, causing the wrapped function to return undefined. The following code properly awaits and returns the result from Controller

type Controller<Args extends any[]> = (...args: Args) => Promise<any>;

export const withDBConnection = <Args extends any[]>(
  Controller: Controller<Args>
): ((...args: Args) => Promise<any>) => {
  return async (...args: Args) => {
    try {
      await dbStartConnection();
      return await Controller(...args); 
    } catch (error) {
      console.log("inside withDBConnection catch block");
      console.log((error as AppError).message);
      throw error; 
    }
  };
};

Edit: Here Controller(...args) is executed, and since it's an async function, it returns a Promise immediately. Immediately , the execution of withDBConnection continues, and the function returns a Promise that resolves to undefined, because there's no explicit return statement.

But by return await Controller(...args) ensures that: The execution of withDBConnection waits for the async operation in Controller to complete.

If you are interested in learning concurrency you definitely need to check out

Upvotes: 3

Related Questions