异常: 嵌套任务透传

重新处理生成器嵌套,需要将子生成器异常抛向父生成器。

当生成器迭代过程发生未捕获异常,生成器将会被关闭,Generator::valid返回false,未捕获异常会从生成器内部被抛向父作用域,嵌套子生成器内部的未捕获异常必须最终被抛向根生成器的calling frame,PHP7中yield-from对嵌套子生成器resume时产生的异常,采取goto try_again传递+while方式层层向上抛出,我们的代码因为递归迭代的原因,未捕获异常需要逆递归栈帧方向层层上抛,性能方便有改进余地。

  1. <?php
  2. final class AsyncTask
  3. {
  4. public function next($result = null, \Exception $ex = null)
  5. {
  6. try {
  7. if ($ex) {
  8. // c. 直接抛出异常
  9. // $ex来自子生成器, 调用父生成器throw抛出
  10. // 这里实现了 try { yield \Generator; } catch(\Exception $ex) { }
  11. // echo "c -> ";
  12. $value = $this->gen->throw_($ex);
  13. } else {
  14. // a2. 当前生成器可能抛出异常
  15. // echo "a2 -> ";
  16. $value = $this->gen->send($result);
  17. }
  18. if ($this->gen->valid()) {
  19. if ($value instanceof \Generator) {
  20. // a3. 子生成器可能抛出异常
  21. // echo "a3 -> ";
  22. $value = (new self($value))->begin();
  23. }
  24. // echo "a4 -> ";
  25. return $this->next($value);
  26. } else {
  27. return $result;
  28. }
  29. } catch (\Exception $ex) {
  30. // !! 当生成器迭代过程发生未捕获异常, 生成器将会被关闭, valid()返回false,
  31. if ($this->gen->valid()) {
  32. // b1. 所以, 当前分支的异常一定不是当前生成器所抛出, 而是来自嵌套的子生成器
  33. // 此处将子生成器异常通过(c)向当前生成器抛出异常
  34. // echo "b1 -> ";
  35. return $this->next(null, $ex);
  36. } else {
  37. // b2. 逆向(递归栈帧)方向向上抛 或者 向父生成器(如果存在)抛出异常
  38. // echo "b2 -> ";
  39. throw $ex;
  40. }
  41. }
  42. }
  43. }
  1. <?php
  2. function newSubGen()
  3. {
  4. yield 0;
  5. throw new \Exception("e");
  6. yield 1;
  7. }
  8. function newGen()
  9. {
  10. try {
  11. $r1 = (yield newSubGen());
  12. } catch (\Exception $ex) {
  13. echo $ex->getMessage();
  14. }
  15. $r2 = (yield 2);
  16. yield 3;
  17. }
  18. $task = new AsyncTask(newGen());
  19. $r = $task->begin(); // output: e
  20. echo $r; // output: 3