never() function

I propose we add a never function, that's defined somewhat like this:

globalThis.AssertionError = class extends Error {}

globalThis.never = function(message = 'The program found itself in a bad state') {
  throw new AssertionError(message)
}

It's purpose it to help self-document when certain things should never happen, and to provide assertions to make sure they really don't happen.

Yes, it's easy for userland to define a function like this, but placing this within JavaScript makes it part of the common language that we can all read and understand instantly. never() looses a lot of it's self-documentation power if the code reader has to look up it's definition to figure out what it does.

Motivating examples

// Throw an error if a particular type isn't handled
messageEvent.addListener(({ type, payload }) => {
  if (type === 'UNDO') {
    // ...
  } else if (type === 'REDO') {
    // ...
  } else never()
})

// Show that certain code paths should never execute
function getPlayer() {
  for (const entity of entities) {
    if (entity.type === 'PLAYER') return entity
  }
  never()
}

// Self-document that you expect something to always be found in the array
// (so you're not left wondering if `player` could be undefined)
const player = entities.find(e => e.type === 'PLAYER') ?? never()

// Assertions
function helperFunction() {
  if (!isInit) never()
  // ...
}

// Show that a function will never return
function runGameLoop() {
  while (true) {
    await new Promise(resolve => resolve(), 100)
    // ...
  }
  never()
}

Prior art

This is very loosely inspired by Elm's never() function. Elm's never() was intended to help the type-system ensure that specific code paths never execute, while the never() I'm proposing here does a runtime-check instead.

Related resources

Error.assert() thread - Love this idea - too bad it hasn't seen much attention yet.
throw as exception proposal

3 Likes

I think this pushes an unnecessary API concept and may introduce some anti-patterns...

const neverBackup = never
never = (...args) => {
  console.trace()
  postError()
  neverBackup()
}

But it is true that you often see this kind of writing in the code
但确实经常能在代码中看到这种写法

const noop = () => { /* doNothing */ }

function elseFn () {
  while (true) {
    if (a) {
      doSomething()
    } else {
      noop()
    }
  }
}

I'm not sure I follow your example. What kind of unnecessary API concepts and anti-patterns are you talking about?

We have a unreachable(val: never, message: string): never in our codebase. It is useful to make sure we made a exhausted match on a variable (the value will become type "never" when all possibility are excluded in Typescript)

2 Likes

This conflicts very heavily with other "never" concepts, like never() from RxJS or a never-resolving promise. I'm also not sure why this isn't just called assert, if that's all it's doing.

I guess it could be assertNever() or something too. I'm not too attached to the name.

Alternatively, we could implement a normal assert function that takes a condition and a message, and if neither parameter is provided, it defaults to throwing an error.

1 Like