Skip to content

Consistent Return Types in TypeScript #2

@boblauer

Description

@boblauer

Imagine we have the following function:

type User = {
  id: string;
  permissions: string[];
};

async function getUserPermissions(userId: string) {
  const user = await db.getUserById(userId);
  if (!user) {
    return { found: false };
  }

  return user.permissions;
}

The above function has the following implicit return type:

Promise<string[] | { found: boolean }>

The issue is that the two return types have no properties in common, so if you try to do the following:

const userPermissions = await getUserPermissions('123');
if (!userPermissions.found) { // compiler error here
  // Handle user not found
} else if (userPermissions.includes('admin'))
  // Allow them access
}

the TypeScript compiler will yell at you with:

Property 'found' does not exist on type 'string[] | { found: boolean; }'

The Fix

The fix will depend on your business logic. In the above scenario, does your app need to distinguish between a user not being found and the user not having the necessary permissions? If not, you should do the following:

async function getUserPermissions(userId: string) {
  const user = await db.getUserById(userId);
  if (!user) {
    return [];
  }

  return user.permissions;
}

This way your function always returns string[]. But if your app does need to distinguish between a user not being found and the user not having the necessary permissions, returning null is probably best:

async function getUserPermissions(userId: string) {
  const user = await db.getUserById(userId);
  if (!user) {
    return null;
  }

  return user.permissions;
}

The return type of Promise<string[] | null> is much easier to work with:

const userPermissions = await getUserPermissions('123');
if (!userPermissions) {
  // Handle user not found
} else if (userPermissions.includes('admin'))
  // Allow them access
}

One Step Further

The easiest way to make sure you know exactly what type your function is returning is to explicitly add the return type:

async function getUserPermissions(userId: string): Promise<string[]> {
  const user = await db.getUserById(userId);
  if (!user) {
    return [];
  }

  return user.permissions;
}

This is a good habit to get into because if you do end up returning something other than string[] in the above function, the TypeScript compiler will yell at you.

If you're using eslint, I would recommend enabling the explicit-function-return-type rule, at least as a warning to start. This will ensure that your functions are returning exactly what you expect, with no surprises.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions