Q:

How to catch exception thrown from a generator and resume iteration?

I have a generator that passes a collection of values into a method and yields the result. The method that is called may return an exception. When this happens, I would like the exception to fall through to the code that calls the generator to handle the exception, and then continue looping the generator.
To illustrate this, the following is an example of generator that will yield 1, throw an \Exception, then yield 3.
/** @var \Generator $gen */
$gen = function () {
    for ($i = 1; $i <= 3; $i++) {
        if ($i == 2) {
            throw new \Exception('Exception thrown for 2');
        }

        yield $i;
    }
};

This is an example of my attempt to run this code such that I can get it to yield 3
$g = $gen();

var_export($g->current());
echo "\n";

try {
    $g->next();
    var_export($g->current());
    echo "\n";
} catch (\Exception $e) {
    echo $e->getMessage() . "\n";
}

try {
    $g->next();
    var_export($g->current());
    echo "\n";
} catch (\Exception $e) {
    echo $e->getMessage() . "\n";
}

The following is the output of the above code.
1
Exception thrown for 2.
NULL

So a repeated calls to next() does nothing and current() will return NULL, where I would like the generator to continue past the Exception so I can get 3.

A:

Throwing the exception inside generator is closing it totally, that is why it returns "NULL" on third iteration. If you try $g->valid() after throwing the exception you will get false as the result.
You should handle the exceptions inside generator, you can even throw them inside generator using $g->throw() method. For more info check the documentation
However, what you are trying to achieve is possible. You can yield the exception, instead of throwing. This way you will not close the generator, and can handle the exception outside.
Try this code:
$gen = function () {
    for ($i = 1; $i <= 3; $i++) {
        // If something wrong happens
        if ($i == 2) {
            // Instead throwing the exception yield it
            // that way we don't close the generator
            yield new \Exception('Exception thrown for 2');
        } else {
            yield $i;
        }
    }
};

And testing it with:
$g = $gen();
for ($i = 0; $i < 3; $i++) {
  $current = $g->current();

  // Instead of catching, check if the yielded value is Exception
  if ($current instanceof \Exception) {
      // Handle the exception directly
      // or throw it with throw $current to handle it in normal try-catch block
      echo $current->getMessage() . "\n";
  } else {
      echo $current . "\n";
  }

  $g->next();
}

Gives you result:
1
Exception thrown for 2
3

