Edit

throw Abort() vs throw new Error()

Reading Recommendation: Overview > RPC

You might wonder: why not just use throw new Error() instead of throw Abort()?

Let's have a look at following example:

// TodoList.telefunc.js
// Environment: server
 
import { Abort, getContext } from 'telefunc'
 
export async function getTodoList() {
  const { user } = getContext()
  if (!user) {
    // ❌ Wrong usae
    throw new Error()
    // ✅ Correct usage
    throw Abort()
  }
 
  // ...
}
// TodoList.telefunc.ts
// Environment: server
 
import { Abort, getContext } from 'telefunc'
 
export async function getTodoList() {
  const { user } = getContext()
  if (!user) {
    // ❌ Wrong usae
    throw new Error()
    // ✅ Correct usage
    throw Abort()
  }
 
  // ...
}

It's expected that the user might not be logged in — using throw Abort() tells Telefunc that the error is expected.

Telefunc interprets throw new Error() as unexpected. Telefunc interprets any error that isn't throw Abort() as a bug in your backend code.

Therefore:

  • Use throw Abort() for wrong public usage of your telefunction.
  • Use throw new Error() for wrong internal usage (i.e. a bug).

Because:

  • throw Abort() doesn't trigger the onBug() hook (whereas throw new Error() does)
  • throw Abort() isn't logged (whereas Telefunc logs throw new Error() via console.error())
  • throw Abort('some error message') => Telefunc sends some error message to the client
  • throw new Error('some error message') => Telefunc doesn't send some error message to the client.

    To prevent leaking sensitive information, Telefunc never forwards unexpected server errors to the client.

Example

// auth/getUser.js
// Environment: server
 
// Note that `auth/getUser.js` isn't a `.telefunc.js` file.
// `getUser()` isn't a telefunction, it's what we call a `getContext()` wrapper.
 
export { getUser }
 
import { Abort, getContext } from 'telefunc'
 
/**
 * Retrieve the current user from context, and abort if the user doesn't have the right permission
 */
function getUser({ permission }) {
  // Wrong internal usage (`getUser()` isn't a telefunction)
  if (!permission) {
    throw new Error('[Wrong getUser() usage] Missing `permission` argument')
  }
  // Prevent typos
  if (!['public', 'admin'].includes(permission)) {
    throw new Error('[Wrong getUser() usage] Unknown `permission` value: ' + permission)
  }
 
  const { user } = getContext()
 
  // User isn't logged in
  if (!user) {
    throw Abort()
  }
 
  // User doesn't have the right permission
  if (permission === 'admin' && !user.isAdmin) {
    throw Abort()
  }
 
  return user
}
// auth/getUser.ts
// Environment: server
 
// Note that `auth/getUser.ts` isn't a `.telefunc.ts` file.
// `getUser()` isn't a telefunction, it's what we call a `getContext()` wrapper.
 
export { getUser }
 
import { Abort, getContext } from 'telefunc'
 
/**
 * Retrieve the current user from context, and abort if the user doesn't have the right permission
 */
function getUser({ permission }: { permission: string }) {
  // Wrong internal usage (`getUser()` isn't a telefunction)
  if (!permission) {
    throw new Error('[Wrong getUser() usage] Missing `permission` argument')
  }
  // Prevent typos
  if (!['public', 'admin'].includes(permission)) {
    throw new Error('[Wrong getUser() usage] Unknown `permission` value: ' + permission)
  }
 
  const { user } = getContext()
 
  // User isn't logged in
  if (!user) {
    throw Abort()
  }
 
  // User doesn't have the right permission
  if (permission === 'admin' && !user.isAdmin) {
    throw Abort()
  }
 
  return user
}

Such getContext() wrapper is a common Telefunc technique explained at Guides > Permissions > getContext() wrapping.

// components/Comment.telefunc.js
// Environment: server
 
import { getUser } from '../auth/getUser'
 
export async function onCommentDelete(id) {
  // Only admins are allowed to delete a comment
  const user = getUser({ permission: 'admin' })
  const comment = await Comment.findOne({ id })
  await comment.delete()
}
// components/Comment.telefunc.ts
// Environment: server
 
import { getUser } from '../auth/getUser'
 
export async function onCommentDelete(id: number) {
  // Only admins are allowed to delete a comment
  const user = getUser({ permission: 'admin' })
  const comment = await Comment.findOne({ id })
  await comment.delete()
}

It is expected that throw Abort() can occur, since onCommentDelete() is a public function anyone can call while not being an admin.

Whereas throw new Error('[Wrong getUser() usage] ...') should never occur: if you make a typo and call getUser({ permission: 'admni' }) then it's an internal bug in your backend code that should be fixed.

See also