Search code examples
typescriptmongoosenext.jsnext-auth

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


Solution

  • 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