3

In converting flow code to typescript an error happens when using iterators. The iterator is missing something

const iter: Iterator<RouteData> = contentMap.routes();
const contentArray: Array<RouteData> = Array.from(iter);

It gives the following error, notice that this points to the second line, so iter is of correct type/the return of contentMap.routes() is an iterator:

Error:(109, 55) TS2769: No overload matches this call.
  Overload 1 of 4, '(iterable: Iterable<RouteData> | ArrayLike<RouteData>): RouteData[]', gave the following error.
    Argument of type 'Iterator<RouteData, any, undefined>' is not assignable to parameter of type 'Iterable<RouteData> | ArrayLike<RouteData>'.
      Property 'length' is missing in type 'Iterator<RouteData, any, undefined>' but required in type 'ArrayLike<RouteData>'.
  Overload 2 of 4, '(arrayLike: ArrayLike<RouteData>): RouteData[]', gave the following error.
    Argument of type 'Iterator<RouteData, any, undefined>' is not assignable to parameter of type 'ArrayLike<RouteData>'.

Why does this happen, and how do I fix it?

The iterator is created somewhere like:

routes():Iterator<RouteData> {
    return this._routes.values();
}

The compile target for typescript is "es6", so maps should be fully supported? Or is it just impossible to create an array from an iterator and have I been doing it all wrong (and was babel just that forgiving)?

1 Answer 1

1

As you probably know, an iterator is an object with a next method used for iterating through an iterable (an object with a [Symbol.iterator] method for getting an iterator from it).

Array.from accepts an iterable (or an array-like), but in that code it thinks it's just getting an iterator.

Most iterators, including all of the ones you get from standard JavaScript methods, are also iterable because they implement [Symbol.iterator]() { return this; }, directly or indirectly through the iterator prototype —but not all do. So it's not safe to assume that all iterators are iterable.

You probably want to update routes to show that it returns something that's both an iterator and iterable:

routes(): Iterator<T> & Iterable<T> {
    return this._routes.values();
}

Then:

const iter = contentMap.routes();
const contentArray = Array.from(iter);

(There's no need for the explicit types on those, TypeScript will infer them.)

Here's a version on the playground demonstrating the problem using Iterator.

Here's that same code using Iterator<T> & Iterable<T> as above.

Sign up to request clarification or add additional context in comments.

4 Comments

FWIW, I go into a fair bit of detail about iterators and iterables in Chapter 6 of my new book out in a couple of months. If you're interested, see my profile for details.
If I inspect the code in javascript map.values() does return an object with next(): exactly an iterator. EDIT: ok now that I removed all type information I notice typescript automatically assigns it IterableIterator type. Begs the question, are any iterators not iterable themselves?
@paul23 - Yeah, I was just realizing -- you'vre using values on what I assume is a Map, and that returns an (iterable) iterator. But Array.from is defined as taking an iterable, not an iterator. Again, most iterators (including all you get from built-in JavaScript functions) are also iterable, but not all are. So you may want to adjust the return type of routes, I'll update the answer.
@paul23 - Fixed it now, sorry for missing the values thing (despite having copied it!) on the first pass. :-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.