Typescript: Typeguard Symbol.iterator

607 Views Asked by At

I’m trying to write a function that accepts either Iterable<T> or `AsyncIterable and maps it:

function map<T, U>(
  fn: (item: T) => U,
  items: Iterable<T> | AsyncIterable<T>,
): Iterable<U> | Iterable<U> & AsyncIterable<U> {
  async function* asyncMapper() {
    for await (const item of items) {
      yield fn(item);
    }
  }

  if (!items[Symbol.iterator]) {
    return asyncMapper()
  }

  return {
    *[Symbol.iterator]() {
      for (const item of items) {
        yield fn(item);
      }
    },

    [Symbol.asyncIterator]: asyncMapper,
  };
}

See playground

The gist of it is that, if the items implements Symbol.iterator we will return an iterable that can iterate both synchronously and asynchronously. However if we don’t we will return only the async iterable.

The problem is that I cannot typeguard the existence of Symbol.iterator because of the error:

error TS7053: Element implicitly has an any type because expression of type symbol can't be used to index type Iterable<T> | AsyncIterable<T>.

Does anyone know how to typeguard Symbol.iterator, or if I’m doing something wrong here?

1

There are 1 best solutions below

0
On BEST ANSWER

Use a user-defined type guard:

function isIterable(x: any): x is Iterable<unknown> {
  return Symbol.iterator in x;
}

function map<T, U>(
  fn: (item: T) => U,
  items: Iterable<T> | AsyncIterable<T>
): Iterable<U> | AsyncIterable<U> {
  async function* asyncMapper() {
    for await (const item of items) {
      yield fn(item);
    }
  }

  if (!isIterable(items)) {
    return asyncMapper()
  }

  return {
    *[Symbol.iterator]() {
      for (const item of items) {
        yield fn(item);
      }
    },

    [Symbol.asyncIterator]: asyncMapper,
  };
}

Playground link