JS `Generator.prototype.return()` / `throw()`:控制 `Generator` 生命周期

各位观众老爷们,早上好/下午好/晚上好!我是你们的老朋友,今天咱们来聊聊 JavaScript 里一个挺有意思,但可能不太常用的东西:Generator.prototype.return()Generator.prototype.throw()。 这俩哥们儿,主要是用来控制 Generator 函数的“生命周期”的,说白了,就是让 Generator 提前结束或者“抛个异常”结束。

一、啥是 Generator?先简单回顾一下

在深入了解 return()throw() 之前,咱们先快速回顾一下 Generator 函数。 如果你已经很熟悉了,可以跳过这部分。

Generator 函数是一种特殊的函数,它可以用 function* 声明。 它和普通函数最大的区别在于:

  • 可以暂停执行: 使用 yield 关键字,可以让 Generator 函数暂停执行,并返回一个值。
  • 可以恢复执行: 通过调用 Generator 对象的 next() 方法,可以恢复 Generator 函数的执行,并传递一个值给它。
  • 迭代器: Generator 函数返回一个迭代器对象,可以用来遍历 Generator 函数产生的值。

举个简单的例子:

function* myGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = myGenerator();

console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }

在这个例子中,myGenerator 是一个 Generator 函数。 每次调用 gen.next(),它都会执行到下一个 yield 表达式,然后暂停并返回一个对象,这个对象包含 value (yield 表达式的值) 和 done (是否执行完毕)。 当所有 yield 表达式都执行完毕后,done 会变成 true

二、Generator.prototype.return():优雅地谢幕

return() 方法的作用是让 Generator 函数提前结束,并返回一个指定的值。 就像演员谢幕一样,总得有个结束的方式。

语法:

gen.return(value);
  • value (可选): 要返回的值。 如果省略,则返回 undefined

返回值:

一个对象,包含 valuedone 属性。 value 是你传入的 value 参数,done 总是 true

示例:

function* myGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = myGenerator();

console.log(gen.next()); // { value: 1, done: false }
console.log(gen.return('提前结束')); // { value: '提前结束', done: true }
console.log(gen.next()); // { value: undefined, done: true } // 已经结束了,再调用 next() 也没用

在这个例子中,我们在第二个 next() 调用之后,调用了 gen.return('提前结束')。 这会导致 Generator 函数立即结束,并返回 { value: '提前结束', done: true }。 之后再调用 next(),得到的 done 仍然是 true,说明 Generator 函数已经彻底结束了。

return() 的一些注意事项:

  • finally 代码块: 如果 Generator 函数中有 finally 代码块,那么在调用 return() 时,finally 代码块会被执行。
  • 已经执行完毕的 Generator: 如果 Generator 函数已经执行完毕,再调用 return() 不会有任何效果。

一个包含 finally 的例子:

function* myGenerator() {
  try {
    yield 1;
    yield 2;
  } finally {
    console.log('finally 代码块被执行了');
  }
  yield 3; // 这句代码不会被执行
}

const gen = myGenerator();

console.log(gen.next()); // { value: 1, done: false }
console.log(gen.return('提前结束')); // { value: '提前结束', done: true }  // finally 代码块被执行
console.log(gen.next()); // { value: undefined, done: true }

在这个例子中,当调用 gen.return('提前结束') 时,finally 代码块会被执行,然后 Generator 函数结束。 最后的 yield 3 代码永远不会被执行。

表格总结 return()

特性 描述
作用 让 Generator 函数提前结束,并返回一个指定的值。
语法 gen.return(value);
返回值 一个对象,包含 value (你传入的 value 参数,如果省略则为 undefined) 和 done: true
finally 如果有 finally 代码块,在 return() 调用时会被执行。
已经结束的 Generator 如果 Generator 已经结束,再调用 return() 不会产生任何效果。

三、Generator.prototype.throw():制造点“意外”

throw() 方法的作用是让 Generator 函数抛出一个异常。 就像舞台上突然掉下来个道具,制造点“意外”。

语法:

gen.throw(error);
  • error: 要抛出的错误对象。 可以是任何 JavaScript 值,通常是一个 Error 实例。

返回值:

如果 Generator 函数内部捕获了异常,那么 throw() 方法会返回一个类似 next() 的对象,包含 valuedone 属性。 如果 Generator 函数没有捕获异常,那么异常会向上传播,直到被外部的 try...catch 块捕获,或者导致程序崩溃。

示例:

function* myGenerator() {
  try {
    yield 1;
    yield 2;
  } catch (e) {
    console.log('捕获到异常:', e);
  }
  yield 3;
}

const gen = myGenerator();

console.log(gen.next()); // { value: 1, done: false }
console.log(gen.throw(new Error('出错了!'))); // 捕获到异常: Error: 出错了!  { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }

在这个例子中,我们在第二个 next() 调用之后,调用了 gen.throw(new Error('出错了!'))。 这会导致 Generator 函数抛出一个 Error 对象。 因为 Generator 函数内部有 try...catch 块,所以异常被捕获了,然后继续执行,直到 yield 3。 最终,Generator 函数执行完毕。

如果没有 try...catch 呢?

function* myGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = myGenerator();

console.log(gen.next()); // { value: 1, done: false }
try {
  console.log(gen.throw(new Error('出错了!'))); // Uncaught Error: 出错了!
} catch (e) {
  console.log('外部捕获到异常:', e);
}
console.log(gen.next()); // { value: undefined, done: true }  // Generator 已经结束

在这个例子中,Generator 函数内部没有 try...catch 块,所以异常会向上传播,直到被外部的 try...catch 块捕获。 如果没有外部的 try...catch 块,程序就会崩溃。

throw() 的一些注意事项:

  • finally 代码块:return() 一样,如果 Generator 函数中有 finally 代码块,那么在调用 throw() 时,finally 代码块也会被执行。
  • 已经执行完毕的 Generator: 如果 Generator 函数已经执行完毕,再调用 throw() 会抛出一个 TypeError 异常。

一个包含 finally 的例子:

function* myGenerator() {
  try {
    yield 1;
    yield 2;
  } finally {
    console.log('finally 代码块被执行了');
  }
  yield 3;
}

const gen = myGenerator();

console.log(gen.next()); // { value: 1, done: false }
try {
  console.log(gen.throw(new Error('出错了!'))); // finally 代码块被执行了  Uncaught Error: 出错了!
} catch (e) {
  console.log('外部捕获到异常:', e);
}
console.log(gen.next()); // { value: undefined, done: true } // Generator 已经结束

在这个例子中,当调用 gen.throw(new Error('出错了!')) 时,finally 代码块会被执行,然后异常向上传播,直到被外部的 try...catch 块捕获。

表格总结 throw()

特性 描述
作用 让 Generator 函数抛出一个异常。
语法 gen.throw(error);
返回值 如果 Generator 内部捕获了异常,则返回类似 next() 的对象。 如果没有捕获,则异常向上传播。
finally 如果有 finally 代码块,在 throw() 调用时会被执行。
已经结束的 Generator 如果 Generator 已经结束,再调用 throw() 会抛出一个 TypeError 异常。

四、return()throw() 的应用场景

虽然 return()throw() 在日常开发中可能不常用,但在某些特定场景下,它们可以发挥重要作用。

  • 资源清理: 可以在 finally 代码块中使用它们来确保资源被正确释放,例如关闭文件、释放数据库连接等。
  • 错误处理: 可以使用 throw() 来模拟 Generator 函数内部发生的错误,方便进行错误处理和调试。
  • 取消操作: 在某些异步操作中,可以使用 return() 来取消正在进行的 Generator 函数。例如,用户取消了上传操作,可以调用 return() 来停止上传过程。

示例:资源清理

function* fileProcessor() {
  let fileHandle = null;
  try {
    fileHandle = openFile('myFile.txt'); // 假设 openFile 返回一个文件句柄
    yield processFile(fileHandle); // 假设 processFile 处理文件内容
  } finally {
    if (fileHandle) {
      closeFile(fileHandle); // 确保文件被关闭
      console.log('文件已关闭');
    }
  }
}

const gen = fileProcessor();
gen.next(); // 开始处理文件

// 假设发生了一些错误,需要提前结束
gen.return(); // 确保文件被关闭

在这个例子中,无论 Generator 函数是否正常执行完毕,finally 代码块都会确保文件被关闭,防止资源泄漏。

示例:取消异步操作

function* uploadFile(file) {
  try {
    const uploadResult = yield startUpload(file); // 假设 startUpload 返回一个 Promise
    console.log('上传成功:', uploadResult);
  } finally {
    if (isUploading) { // 假设 isUploading 是一个全局变量,表示是否正在上传
      cancelUpload(); // 假设 cancelUpload 取消上传操作
      console.log('上传已取消');
    }
  }
}

let isUploading = false; // 全局变量,表示是否正在上传

function startUpload(file) {
  return new Promise(resolve => {
    isUploading = true;
    setTimeout(() => {
      isUploading = false;
      resolve('上传完成');
    }, 2000);
  });
}

function cancelUpload() {
  console.log('正在取消上传...');
  // 实际的取消上传逻辑
}

const gen = uploadFile('myFile.txt');
gen.next(); // 开始上传

// 假设用户取消了上传
setTimeout(() => {
  gen.return(); // 取消上传
}, 1000);

在这个例子中,如果用户在上传过程中取消了操作,我们可以调用 gen.return() 来停止上传过程,并在 finally 代码块中取消正在进行的上传操作。

五、总结

Generator.prototype.return()Generator.prototype.throw() 是用来控制 Generator 函数生命周期的两个重要方法。

  • return() 让 Generator 函数提前结束,并返回一个指定的值。
  • throw() 让 Generator 函数抛出一个异常。

虽然它们在日常开发中可能不常用,但在资源清理、错误处理、取消操作等特定场景下,可以发挥重要作用。 掌握它们,可以让你更好地控制 Generator 函数的行为,写出更健壮、更可靠的代码。

希望今天的讲座对大家有所帮助! 谢谢大家!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注