Typescript: for-of does not work with some DOM collections when target is ES6

Created on 10 Apr 2015  ·  42Comments  ·  Source: microsoft/TypeScript

Update: This issue is now only for the following collections:

  • MediaList
  • StyleSheetList
  • CSSRuleList

Originally it was about NodeList as well, but that was fixed (along with DOMTokenList) in #3393


Original:

var paragraphs = document.querySelectorAll("p");
for (let p of paragraphs) {
    console.log(p);
}
foo.ts(2,15): error TS2488: The right-hand side of a 'for...of' statement must have a '[Symbol.iterator]()' method that returns an iterator.

Seems it just needs an update to lib.es6.d.ts to add the [Symbol.iterator()] to all the collections that have an indexer and length property. The ones I found that have it in Nightly are below. The rest were either IE-only or didn't exist. Removed my list since it seems FF has more than the specs allow. See https://github.com/zloirock/core-js/issues/137#issuecomment-159531781 for a more accurate list.

Bug lib.d.ts help wanted

Most helpful comment

This can be closed now as lib.dom.iterable.d.ts have iterators for MediaList, StyleSheetList, CSSRuleList, and many more.

All 42 comments

@zhengbli would this be covered in your change?

(This would also require making two versions of dom.d.ts - for ES5 and ES6.)

yup. that is correct.

This should also be supported in ES3/ES5 since the emitted loop is just looping over the indexes.

iterators relay on symbols and these are defined in ES6. I think what we need is to allow iterating ArrayLike in ES3/ES5 (#2862).

@djarekg That is not correct. It can also require converting NodeList to an array first (with slice) to handle the livelist getting modified during iteration. See https://github.com/Microsoft/TypeScript/issues/2696

Please do not forget to include in this list TouchList interface. Thanks.

@SaschaNaz has added support for:

  • NodeList
  • NodeListOf
  • DOMTokenList

The other interfaces do not seem to be iterable in the spec. @Arnavion any ideas?

They're not iterable<> but they _are_ ArrayClass, eg CSSRuleList. That means their prototype is supposed to be an Array instead of Object and so they have all the properties of Array including [Symbol.iterator], although no browser has actually gone _that_ far, not even Nightly.

NodeList is also supposed to be ArrayClass instead of iterable<>, but Chrome had problems with it and (temporarily?) abandoned it. FF has an open bug to convert NodeList from iterable to ArrayClass but so far nothing has happened. I don't know what browsers' plan for these "ArrayClass" interfaces - I would guess it's likely that all ArrayClass interfaces will eventually be iterable even if they aren't full-blown Arrays.

The array iterator Array.prototype[Symbol.iterator] is also usable for all DOM collections, and this is what Nightly does. For example, CSSRuleList.prototype[Symbol.iterator] === Array.prototype[Symbol.iterator] is true in Nightly. This is intentional - ArrayIterator is supposed to work with ArrayLikes. So even in browsers where DOM collection prototypes don't already have a Symbol.iterator property, assigning it with, say CSSRuleList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];, works and makes them iterable with for-of. This would be an argument for TS to allow for-of for these types. The user just needs to add a polyfill to assign the [Symbol.iterator] property on all the DOM list prototypes they're interested in iterating with for-of. But these will have to be custom polyfills - I don't know any polyfill library that adds Symbol.iterator properties to _all_ DOM collections. Babel (core-js) does it but only for NodeList.

On the other hand, Array.from() works for them since they are array-likes, and even in TS they implicitly extend ArrayLike<T> so Array.from()'s signature is not a problem. So that's an argument against allowing for-of for these types in TS.

So basically, from the user's point of view, either they use a polyfill for Array.from, and use Array.from to convert all their DOM collections before using for-of. This requires no change from TS, and such a polyfill already exists. (Then there was no point to adding [Symbol.iterator] to NodeList either, but anyway...)

Or, TS allows for-of with all DOM collections, and the user uses a polyfill that adds [Symbol.iterator] properties to all those collections. This does require change from TS, and such a polyfill doesn't already exist.

I don't have an opinion either way.

@Arnavion

Neither NodeList and DOMTokenList are supposed to be Array subclasses. They are only supposed to be iterable, as in:

  • JavaScript:

js NodeList.prototype[Symbol.iterator] = function* () { // ... }

  • Python:

py class NodeList: # ... def __iter__(self): # ...

  • Java:

java import java.util.*; public class NodeList implements Iterable<Node> { // ... Iterator<Node> iterator() { // ... } }

  • Scala:

scala import scala.collection.{Iterable, Iterator} class NodeList extends Iterable[Node] { // ... def iterator: Iterator[Node] = // ... }

@impinball Yes they are supposed to be, per the spec. I gave links for the other CSSOM objects in the previous post, and the link for NodeList is here where you can also see it's described as ArrayClass.

I already explained that no browser has actually implemented them as ArrayClass.

Edit: I see you're specifically talking about the whatwg's spec. Sure, that's the one browsers are more likely to implement anyway. However also note that whether they are ArrayClass or not is not relevant to this issue.

@Arnavion I see what you're talking about in the edit...This bug is actually about the following problem:

interface Foo {
  *[Symbol.iterator](): Iterator<any>;
}

declare class Bar {
  *[Symbol.iterator](): Iterator<any>;
}

declare let baz: {
  *[Symbol.iterator](): Iterator<any>;
};

declare let foo: Foo;
let bar = new Bar();

for (let _ of foo) {} // Fail?
for (let _ of bar) {} // Fail?
for (let _ of baz) {} // Fail?

Does that repro in any of the three cases? I don't have a dev version handy to test it.

Without the * (asterisks are not needed for declarations), yes all of those compile, as they're supposed to. This bug is about adding Symbol.iterator properties to the DOM types in the first place.

Okay...that is probably trivial

Unless it's something else...the declarations already exist.

That file was merged on June 5.

Please read this thread...

It type-checks for me with the master branch with --target es6. I just checked on my machine.

I'm using core-js with target to ES5 in my project, but is impossible use for...of with NodeList type.

let nodes = document.querySelectorAll(".nodes");

for (let node of nodes) {
}

When I try to compile it, I get the follow error:

error TS2495: Type 'NodeList' is not an array type or a string type.

@dashaus This issue is for when the target is ES6. See #2696 for when the target is ES5.

I don't know any polyfill library that adds Symbol.iterator properties to all DOM collections. Babel (core-js) does it but only for NodeList.

core-js doesn't add it because of Chromium 38-40 bug. We can ignore this problem in obsolete desktop browsers, but it's also in Android 5.1. Possible replace Symbol.iterator to string, but it's requires wrapping Symbol and replacement all iterators. If it is really needed, I can play with it :)

@zloirock Does this commit actually add support?

And I know there's still an industry for Chrome apps and Firefox-specific add-ons. And the latest few versions of those don't have this bug.

@impinball only HTMLCollection, only for engines w/o this bug and, for this reason, it's not documented.

Proposal for target < ES6

  1. Fix the type checker for for..of to check the type of the subject. For ES6 collections, it's IterableIterator<T> (lib/lib.es6.d.ts). Until v2.1, issue an error or warning. For and beyond v2.1 and for target < es6, emit code that uses Symbol instead (example of how Babel does this for target < ES6)
  2. Hoist generate vars to the enclosing function and avoid generating duplicate var declarations.

Currently:

input

for (let entry of entries) {
}
for (let entry of entries) {
}

output

for (var _i = 0, _a = entries; _i < _a.length; _i++) {
    var entry = _a[_i];
}
for (var _b = 0, _c = entries; _b < _c.length; _b++) {
    var entry = _c[_b];
}

There're two var declarations declaring the same symbol.

the generated code looks like this

The result should be just ES6 but the generated one is ES5. I think your compiler setting is wrong somewhere...

@SaschaNaz: Oh shoot. You're right. I do have "target": "ES2015" in tsconfig.json but then the build script I'm using explicitly sets --target=ES5

@mhegazy Please consider Microsoft/TSJS-lib-generator#235 too ;)

This has had no activity for a year - is it still considered a problem?

The issue still appears to be extant:

const collection: HTMLCollection = getCollection();
const arrayCopy = [...collection]; // (TS) Type must have a '[Symbol.iterator]()' method that returns an iterator.

@KeithHenry I've been actively fixing wrong types on https://github.com/Microsoft/TSJS-lib-generator, I think this also will be fixed within months, or you can send your own PR there.

@saschanaz cheers, but that already has HTMLCollection configured as iterable. What else needs to change?

@KeithHenry That file should replace lib.dom.iterable.d.ts but the upstream does not yet support methods including entries(). Coming Soon™️, though!

What do you mean by upstream does not support, do you mean iterators, in general, are broken? or is this purely a type definition issue?

Ah, by upstream I mean this one.

This can be closed now as lib.dom.iterable.d.ts have iterators for MediaList, StyleSheetList, CSSRuleList, and many more.

This problem still exists for me when trying to iterate over NodeListOf<Element>. A NodeList is _always_ iterable. Period.

Some code:

const triggers = element.querySelectorAll('.trigger');
for (const trigger of triggers) {
    console.log(trigger);
}

The error is on triggers in VS Code, using typescript 3.2.2.

The error:

[ts] Type 'NodeListOf<Element>' is not an array type or a string type. [2495]

Technically correct. A NodeList is indeed neither an array nor a string. But for..of works on a great many more kinds of objects, including NodeList.

The above code compiles and works fine. So why complain about something that plainly isn't the case?

@thany Ensure the target is ES6 or higher. Currently TS does not support for-of on general iterable objects when the target is ES5/ES3.

Or enable --downlevelIteration

First of all, why does TS need to support it at all? It just needs to output the transpiled JS and let JS handle whether the object can be looped over or not.

Apart from that, NodeList is _always_ iterable, in every browser. Some require a indexed for-loop, newer browser suport for..of. But either way the object can be looped over perfectly fine.

If the target is ES5, produce a classic for-loop.
If the target is ES6 or higher, produce a for..of loop.

I don't see how this is complicated in any way.

@thany Have you tried --downlevelIteration (introduced in TS2.3, I forgot about it 😅)? I think that should work.

I've enabled downlevelIteration (in our tsconfig.json) which doesn't produce any difference. The code still builds and works fine, but I still get the error in VS Code.

Also since the update, VS Code has switched to TS 3.3.1, in case you didn't know.

You may file a new issue with a repro then.


From: Martijn Saly notifications@github.com
Sent: Tuesday, February 12, 2019 6:59:45 PM
To: Microsoft/TypeScript
Cc: Kagami Sascha Rosylight; Mention
Subject: Re: [Microsoft/TypeScript] for-of does not work with some DOM collections when target is ES6 (#2695)

I've enabled downlevelIteration which doesn't produce any difference. The code still builds and works fine, but I still get the error in VS Code.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHubhttps://nam03.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2FMicrosoft%2FTypeScript%2Fissues%2F2695%23issuecomment-462694287&data=02%7C01%7C%7C91a4119c8b1a4177826908d690d0d115%7C84df9e7fe9f640afb435aaaaaaaaaaaa%7C1%7C0%7C636855623858410198&sdata=PxkoLBSyS9NJU%2BduETuD0QoOhuRFm8VWZhHgwz52hIo%3D&reserved=0, or mute the threadhttps://nam03.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fnotifications%2Funsubscribe-auth%2FADPUTsr8pogzyuxsxABnatMGWO9V1y-yks5vMpCRgaJpZM4D9m6v&data=02%7C01%7C%7C91a4119c8b1a4177826908d690d0d115%7C84df9e7fe9f640afb435aaaaaaaaaaaa%7C1%7C0%7C636855623858420212&sdata=CXMOuYo2tVV738FKLbb1mWyg5X1Zen1tlLh1BZ1ekU0%3D&reserved=0.

Was this page helpful?
0 / 5 - 0 ratings