throwing/breaking inside a for-of loop does not call iterator.throw()

classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

throwing/breaking inside a for-of loop does not call iterator.throw()

Marius Gundersen
This is something I stumbled across while looking into the for-of-with (aka continue-with/break-with). It seems like throwing inside a for-of loop does not call the iterators throw method, it calls the return method and it seems to disregard the result of the method. For eample:

```js
var gen = () => ({
  x:0,
  [Symbol.iterator](){
    return this;
  },
  next(p){
    console.log('next', p, this.x);
    return {done: false, value: this.x++};
  },
  return(p){
    console.log('return', p);
    return {done: false, value: 'return'};
  },
  throw(p){
    console.log('throw', p);
    return {done: false, value: 'throw'};
  }
});

for(const g of gen()){ throw 'test' }
// next undefined 0
// return undefined
```

Shouldn't it call the throw method on the iterator, and then use the return value to determine if it will actually return from the loop or not? This would be useful for generators, which can catch the error and deal with it, and continue to yield values even after throw and return have been called:

```
function* gen() {
  while(true) {
    try {
       yield 42;
    } catch(e) {
      console.log('Error caught!');
    }
  }
}

var g = gen();
g.next();
// { value: 42, done: false }
g.throw('Something went wrong');
// "Error caught!"
// { value: 42, done: false }
```

Unfortunately this example behaves differently if you try to loop over gen() in a for-of loop

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: throwing/breaking inside a for-of loop does not call iterator.throw()

Logan Smyth
As far as I know, `.throw` exists as a way to inject behavior into an in-progress iterator. See in https://tc39.github.io/ecma262/#table-54 for "throw"

> Invoking this method notifies the Iterator object that the caller has detected an error condition.

But the key thing is that the spec never calls `.throw` directly. It defines that `.throw` exists, and it defines how it works with generator functions, by causing the currently yielded statement to essentially do `throw err;`. It also ensures that `yield*` properly propagates the throw behavior through into delegated iterators. Beyond that, it does nothing, because it doesn't really define what "detected an error condition" means.

The answer to your question, from my point of view anyway, is that it isn't designed for what you think. Throwing an error inside a loop body is an error, but it's not an error condition for the iterator itself, it's just an error condition for the loop body. The only time `.throw` runs is if something in your code itself calls it.


On Wed, Aug 23, 2017 at 1:40 PM, Marius Gundersen <[hidden email]> wrote:
This is something I stumbled across while looking into the for-of-with (aka continue-with/break-with). It seems like throwing inside a for-of loop does not call the iterators throw method, it calls the return method and it seems to disregard the result of the method. For eample:

```js
var gen = () => ({
  x:0,
  [Symbol.iterator](){
    return this;
  },
  next(p){
    console.log('next', p, this.x);
    return {done: false, value: this.x++};
  },
  return(p){
    console.log('return', p);
    return {done: false, value: 'return'};
  },
  throw(p){
    console.log('throw', p);
    return {done: false, value: 'throw'};
  }
});

for(const g of gen()){ throw 'test' }
// next undefined 0
// return undefined
```

Shouldn't it call the throw method on the iterator, and then use the return value to determine if it will actually return from the loop or not? This would be useful for generators, which can catch the error and deal with it, and continue to yield values even after throw and return have been called:

```
function* gen() {
  while(true) {
    try {
       yield 42;
    } catch(e) {
      console.log('Error caught!');
    }
  }
}

var g = gen();
g.next();
// { value: 42, done: false }
g.throw('Something went wrong');
// "Error caught!"
// { value: 42, done: false }
```

Unfortunately this example behaves differently if you try to loop over gen() in a for-of loop

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss



_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss