The first time I've use a JS Symbol

Intro

Javascript has symbols. Even if this is widely known, they are not something one uses frequently. I’ve finally found one problem that could be solved with a Symbol (even if it could have been done differerently).

The problem

The problem was simple: I needed to be able to set a timeout for a whole array of functions that returned promises. While that’s easy to implement, I wanted that the behaviour after a timeout was not simply to throw an Error, but to handle the timeout differently from promise to promise.

Also, I wanted to be able to write it as a wrapper so that it could work with any promise returning function. I use Typescript, so the types of the wrapper are important, but it works in the same way even without them.

The solution

The idea is simple: wrap the function in a new function that starts an async timeout once called. Use Promise.race to check which promise resolves first (the original promise or the timeout) and return something like both the result and if the promise timed out.

This is the moment when the Symbol can be useful: since I wanted be able to distinguish between the results from the original promise and my timeout promise, but without knowing in advance what the result is, I needed something that can only be equal to itself.

And while that can be achieved with any other means (objects, random numbers, even random strings), I think that this is one of the perfect use cases for Symbols. So the timeout promise generated inside the wrapper returns a symbol, and if Promise.race returns the same symbol, then it’s clear that the promise has timed out.

The code

The code is quite simple:

function timeoutPromise<T>(promise: Promise<T>, maxTimeout: number): Promise<[T, boolean]> {
  const agonistResult = Symbol()
  const agonistPromise = new Promise<Symbol>((r) => setTimeout(() => r(agonistResult), maxTimeout))
  const race = Promise.race<T | Symbol>([agonistPromise, promise])
  return race.then((results) => {
    const hasTimedOut = isSymbol(results) && results === agonistResult
    return hasTimedOut ? [null, true] : [results, false]
  })
}

This wrapper returns an array with in the first position the result of the wrapped promise if it resolved in time, or null, and in the second position a boolean that is true if the wrapped promise has timed out.