In javascript, iterators are allowed to have throw(error)
and return(value)
methods. return(value)
gives the iterator a chance to see value
, and is expected to return {value: value, done: true}
. throw(error)
gives the iterator a chance to see and potentially catch the error. If the error is caught, throw
should return the next value. If the exception is not caught, it should be equivalent to return(undefined)
. Here's an example of these mechanics in action:
function* test() {
try {
yield 1;
} catch(e) {}
const fromConsumer = yield 2;
yield fromConsumer;
// I know no way to access the value passed to return in a generator
}
const iter = test()[Symbol.iterator]();
console.log(iter.next());
console.log(iter.throw(new Error('catch me')));
console.log(iter.next(9));
console.log(iter.next());
console.log(iter.return(0));
(Same thing as a jsfiddle)
My question is: Why? Does anyone have a defensible use case for this reversal of control API for iterators? Under what conditions does it actually make sense for an iterator to handle an error incurred while consuming it? When would you want to pass a value to return given that it is certain to end the iterator and can't further influence the behavior of the iterator API?
I'll say that the one use case I'm aware of is redux-saga, where they have leveraged the inversion of control API of iterators seemingly as a poor man's async/await. If someone is familiar with design or usage of that tool, are there other benefits to their choice?