Generator.prototype.throw()

Generator 函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。

  1. var g = function* () {
  2. try {
  3. yield;
  4. } catch (e) {
  5. console.log('内部捕获', e);
  6. }
  7. };
  8. var i = g();
  9. i.next();
  10. try {
  11. i.throw('a');
  12. i.throw('b');
  13. } catch (e) {
  14. console.log('外部捕获', e);
  15. }
  16. // 内部捕获 a
  17. // 外部捕获 b

上面代码中,遍历器对象i连续抛出两个错误。第一个错误被 Generator 函数体内的catch语句捕获。i第二次抛出错误,由于 Generator 函数内部的catch语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了 Generator 函数体,被函数体外的catch语句捕获。

throw方法可以接受一个参数,该参数会被catch语句接收,建议抛出Error对象的实例。

  1. var g = function* () {
  2. try {
  3. yield;
  4. } catch (e) {
  5. console.log(e);
  6. }
  7. };
  8. var i = g();
  9. i.next();
  10. i.throw(new Error('出错了!'));
  11. // Error: 出错了!(…)

注意,不要混淆遍历器对象的throw方法和全局的throw命令。上面代码的错误,是用遍历器对象的throw方法抛出的,而不是用throw命令抛出的。后者只能被函数体外的catch语句捕获。

  1. var g = function* () {
  2. while (true) {
  3. try {
  4. yield;
  5. } catch (e) {
  6. if (e != 'a') throw e;
  7. console.log('内部捕获', e);
  8. }
  9. }
  10. };
  11. var i = g();
  12. i.next();
  13. try {
  14. throw new Error('a');
  15. throw new Error('b');
  16. } catch (e) {
  17. console.log('外部捕获', e);
  18. }
  19. // 外部捕获 [Error: a]

上面代码之所以只捕获了a,是因为函数体外的catch语句块,捕获了抛出的a错误以后,就不会再继续try代码块里面剩余的语句了。

如果 Generator 函数内部没有部署try...catch代码块,那么throw方法抛出的错误,将被外部try...catch代码块捕获。

  1. var g = function* () {
  2. while (true) {
  3. yield;
  4. console.log('内部捕获', e);
  5. }
  6. };
  7. var i = g();
  8. i.next();
  9. try {
  10. i.throw('a');
  11. i.throw('b');
  12. } catch (e) {
  13. console.log('外部捕获', e);
  14. }
  15. // 外部捕获 a

上面代码中,Generator 函数g内部没有部署try...catch代码块,所以抛出的错误直接被外部catch代码块捕获。

如果 Generator 函数内部和外部,都没有部署try...catch代码块,那么程序将报错,直接中断执行。

  1. var gen = function* gen(){
  2. yield console.log('hello');
  3. yield console.log('world');
  4. }
  5. var g = gen();
  6. g.next();
  7. g.throw();
  8. // hello
  9. // Uncaught undefined

上面代码中,g.throw抛出错误以后,没有任何try...catch代码块可以捕获这个错误,导致程序报错,中断执行。

throw方法抛出的错误要被内部捕获,前提是必须至少执行过一次next方法。

  1. function* gen() {
  2. try {
  3. yield 1;
  4. } catch (e) {
  5. console.log('内部捕获');
  6. }
  7. }
  8. var g = gen();
  9. g.throw(1);
  10. // Uncaught 1

上面代码中,g.throw(1)执行时,next方法一次都没有执行过。这时,抛出的错误不会被内部捕获,而是直接在外部抛出,导致程序出错。这种行为其实很好理解,因为第一次执行next方法,等同于启动执行 Generator 函数的内部代码,否则 Generator 函数还没有开始执行,这时throw方法抛错只可能抛出在函数外部。

throw方法被捕获以后,会附带执行下一条yield表达式。也就是说,会附带执行一次next方法。

  1. var gen = function* gen(){
  2. try {
  3. yield console.log('a');
  4. } catch (e) {
  5. // ...
  6. }
  7. yield console.log('b');
  8. yield console.log('c');
  9. }
  10. var g = gen();
  11. g.next() // a
  12. g.throw() // b
  13. g.next() // c

上面代码中,g.throw方法被捕获以后,自动执行了一次next方法,所以会打印b。另外,也可以看到,只要 Generator 函数内部部署了try...catch代码块,那么遍历器的throw方法抛出的错误,不影响下一次遍历。

另外,throw命令与g.throw方法是无关的,两者互不影响。

  1. var gen = function* gen(){
  2. yield console.log('hello');
  3. yield console.log('world');
  4. }
  5. var g = gen();
  6. g.next();
  7. try {
  8. throw new Error();
  9. } catch (e) {
  10. g.next();
  11. }
  12. // hello
  13. // world

上面代码中,throw命令抛出的错误不会影响到遍历器的状态,所以两次执行next方法,都进行了正确的操作。

这种函数体内捕获错误的机制,大大方便了对错误的处理。多个yield表达式,可以只用一个try...catch代码块来捕获错误。如果使用回调函数的写法,想要捕获多个错误,就不得不为每个函数内部写一个错误处理语句,现在只在 Generator 函数内部写一次catch语句就可以了。

Generator 函数体外抛出的错误,可以在函数体内捕获;反过来,Generator 函数体内抛出的错误,也可以被函数体外的catch捕获。

  1. function* foo() {
  2. var x = yield 3;
  3. var y = x.toUpperCase();
  4. yield y;
  5. }
  6. var it = foo();
  7. it.next(); // { value:3, done:false }
  8. try {
  9. it.next(42);
  10. } catch (err) {
  11. console.log(err);
  12. }

上面代码中,第二个next方法向函数体内传入一个参数 42,数值是没有toUpperCase方法的,所以会抛出一个 TypeError 错误,被函数体外的catch捕获。

一旦 Generator 执行过程中抛出错误,且没有被内部捕获,就不会再执行下去了。如果此后还调用next方法,将返回一个value属性等于undefineddone属性等于true的对象,即 JavaScript 引擎认为这个 Generator 已经运行结束了。

  1. function* g() {
  2. yield 1;
  3. console.log('throwing an exception');
  4. throw new Error('generator broke!');
  5. yield 2;
  6. yield 3;
  7. }
  8. function log(generator) {
  9. var v;
  10. console.log('starting generator');
  11. try {
  12. v = generator.next();
  13. console.log('第一次运行next方法', v);
  14. } catch (err) {
  15. console.log('捕捉错误', v);
  16. }
  17. try {
  18. v = generator.next();
  19. console.log('第二次运行next方法', v);
  20. } catch (err) {
  21. console.log('捕捉错误', v);
  22. }
  23. try {
  24. v = generator.next();
  25. console.log('第三次运行next方法', v);
  26. } catch (err) {
  27. console.log('捕捉错误', v);
  28. }
  29. console.log('caller done');
  30. }
  31. log(g());
  32. // starting generator
  33. // 第一次运行next方法 { value: 1, done: false }
  34. // throwing an exception
  35. // 捕捉错误 { value: 1, done: false }
  36. // 第三次运行next方法 { value: undefined, done: true }
  37. // caller done

上面代码一共三次运行next方法,第二次运行的时候会抛出错误,然后第三次运行的时候,Generator 函数就已经结束了,不再执行下去了。