学习 ES6 生成器 ( Generator ) :把握高雅的异步编程利器

一. 导言

在软件开发中,异步编程是一个常见的需求。异步编程答应程序在履行等待耗时操作时不被阻塞,而是持续履行其他使命,进步程序的响应性和性能。然而,传统的异步编程办法(如回调函数事件监听)往往会导致代码复杂度添加可读性下降等问题,给开发者带来了许多困扰和应战。

ES6 生成器(Generator)是 JavaScript 语言新增的一种特别函数类型,它供给了一种高雅的解决方案来处理异步编程。生成器使得开发者可以用同步的线性代码风格来书写异步操作,代码愈加简练、可读性更高,一起还可以减少回调地狱和处理并发操作的复杂性。

本文将介绍 ES6 生成器的特性和效果。从零开端,学习生成器的语法、运用办法以及在异步编程中的运用办法。经过运用生成器,可以完结更高雅、更高效的异步编程办法,然后加快开发功率。在接下来的内容中,咱们一同探究生成器的奥妙,并学习如何将其运用于实践项目中。

二. 生成器的基础知识

1. 生成器的界说和语法

生成器(Generator)是一个特别的函数类型,可以在履行进程中暂停并持续履行。它经过运用 yield 要害字来界说中断点,每次调用生成器的 next()办法都会从上一个中止点持续履行,直到遇到下一个 yield 要害字或函数完毕。

生成器的界说格局如下:

function* generatorName() {
  // 生成器函数体
}

在生成器函数体内,可以运用 yield 要害字来界说中止点,该要害字后边的表达式的值将作为生成器的回来值。

例如,下面是一个简略的生成器示例,生成器名为 counter:

function* counter() {
  let count = 0;
  while (true) {
    yield count;
    count  ;
  }
}

在上述示例中,counter 生成器会无限循环并运用 yield 要害字将当时的 count 值回来。每次调用生成器的 next() 办法时,它会在 yield 句子处中止,并将 yield 后边的值作为 next() 的回来成果。然后,下一次调用 next() 办法时,生成器会从前次中止的地方持续履行,更新 count 值并再次中止,如此往复。

生成器的语法运用了特别的 function*声明来界说生成器函数,一起还可以运用 yield 要害字来操控生成器的履行流程。生成器函数的调用会回来一个迭代器目标,经过 next() 办法对生成器进行手动迭代。

需求留意的是,生成器函数界说时需求在函数要害字 function 后边加上星号(*),以标识该函数为生成器函数。别的,yield 要害字只能在生成器函数内部运用。

2. yield 要害字的效果和运用办法

yield 要害字在生成器函数中的效果是界说一个中止点,暂停生成器的履行,并回来一个指定的值。

生成器函数中可以有多个 yield 句子,每次调用生成器的 next() 办法时,它会在当时的 yield 句子处中止。当再次调用 next() 办法时,生成器会从前次中止的地方持续履行,直到遇到下一个 yield 句子或函数完毕。

yield 句子的语法如下:

yield expression;

其间,expression 是一个表达式,它的值将作为生成器的回来值。可以运用 yield 回来恣意类型的值,例如数字、字符串、目标等。

下面是一个运用 yield 的简略示例:

function* generator() {
  yield "Hello";
  yield "World";
  yield 2023;
}
const gen = generator();
console.log(gen.next()); // {value: 'Hello', done: false}
console.log(gen.next()); // {value: 'World', done: false}
console.log(gen.next()); // {value: 2023, done: false}
console.log(gen.next()); // {value: undefined, done: true}

学习 ES6 生成器 ( Generator ) :把握高雅的异步编程利器

在上述示例中,generator 生成器函数内部有三个 yield 句子。每次调用 gen.next()办法时,生成器会从上一次中止的地方持续履行,并将当时 yield 句子后边的值作为回来成果。当一切的 yield 句子都履行完后,生成器的 done 特点会变为 true,表明生成器履行完毕。

需求留意的是,yield 要害字只能在生成器函数内部运用,用于界说中止点。在其他函数或普通的代码块中运用 yield 要害字会导致语法错误。别的,yield 要害字后边可以跟恣意的表达式,包括函数调用核算表达式等。

3. next() 办法的调用和履行进程

next()办法用于履行生成器函数,并回来一个目标,包含生成器的履行成果。它的履行进程如下:

  1. 调用生成器函数,生成一个迭代器目标:

    const gen = generator();
    
  2. 调用迭代器目标的 next() 办法:

    const result = gen.next();
    
  3. 生成器函数从上一次 yield 句子处开端履行,直到遇到下一个 yield 句子或函数完毕。

  4. 假如遇到 yield 句子,则将 yield 后边的值作为 next() 办法的回来成果。一起,暂停生成器的履行,保存生成器的上下文状况。

  5. 假如生成器函数履行完一切的 yield 句子,或许履行到函数的完毕,生成器的 done 特点将变为 true,表明生成器履行完毕。此刻,next()办法的回来成果中的 value 特点为 undefined

  6. 假如需求再次履行生成器,可以再次调用 next() 办法,并重复进程 3-5。

下面是一个详细的示例:

function* generator() {
  console.log("Start");
  yield "Hello";
  console.log("Middle");
  yield "World";
  console.log("End");
}
const gen = generator();
console.log(gen.next()); // 输出: Start, {value: 'Hello', done: false}
console.log(gen.next()); // 输出: Middle, {value: 'World', done: false}
console.log(gen.next()); // 输出: End, {value: undefined, done: true}
console.log(gen.next()); // 输出: {value: undefined, done: true}

在上述示例中,调用 gen.next()办法会顺次履行生成器函数内的代码。每次调用 next()办法时,生成器会从上一次 yield 句子处持续履行,并将 yield 后边的值作为回来成果。当生成器履行完一切的 yield 句子后,done 特点为 true,表明生成器履行完毕。再次调用 next() 办法只会回来{value: undefined, done: true}。如下图所示:

学习 ES6 生成器 ( Generator ) :把握高雅的异步编程利器

需求留意的是,next() 办法的调用不一定需求在代码履行的开端,可以在恣意阶段调用,生成器会从上一次中止的地方持续履行。每次调用 next() 办法时,生成器函数会履行一次,直到遇到下一个 yield 句子或函数完毕。

三. 运用生成器进行同步迭代

生成器函数和可迭代目标之间存在紧密的关系。生成器函数可以用来创立可迭代目标,而可迭代目标则可以被用于循环迭代或传递给消费者函数进行处理。

生成器函数界说为一个带有 yield 句子的函数,在调用生成器函数时,它回来一个生成器目标。生成器目标是可迭代目标的一种,因而可以在 for...of 循环中运用,或许调用可迭代目标的内置迭代器办法(如 next()Symbol.iterator)。

下面是一个示例,展示了生成器函数和可迭代目标之间的关系:

function* generator() {
  yield "Hello";
  yield "World";
}
const iterable = generator();
for (const value of iterable) {
  console.log(value);
}
// 输出:
// Hello
// World

在上述示例中,生成器函数generator回来一个生成器目标,该生成器目标可以被迭代。咱们将生成器目标赋值给变量iterable,然后在 for...of 循环中运用 iterable进行迭代。每次迭代时,生成器函数会从上一次 yield 句子处开端履行,并将 yield 后边的值作为迭代成果。如下图所示:

学习 ES6 生成器 ( Generator ) :把握高雅的异步编程利器

除了运用 for...of 循环外,咱们也可以运用可迭代目标的内置迭代器办法进行手动迭代,如下所示:

function* generator() {
  yield "Hello";
  yield "World";
}
const iterable = generator();
console.log(iterable.next()); // {value: 'Hello', done: false}
console.log(iterable.next()); // {value: 'World', done: false}
console.log(iterable.next()); // {value: undefined, done: true}

可迭代目标的内置迭代器办法 next() 会使生成器函数从上一次中止的地方开端履行,并回来迭代成果。每次调用 next() 办法时,生成器函数会履行一次,直到遇到下一个 yield 句子或函数完毕。如下图所示:

学习 ES6 生成器 ( Generator ) :把握高雅的异步编程利器

总结来说,生成器函数是创立可迭代目标的一种办法,而可迭代目标可以经过迭代器办法进行迭代和取值,完结了迭代器协议。生成器函数的灵活性和方便性使得可以轻松地完结慵懒核算、无限序列等功能。一起,可迭代目标的支持使得咱们可以更方便地对其进行迭代和处理。

四. 异步编程中的生成器运用

1. 异步操作的挂起和康复

生成器在异步操作中的挂起和康复是经过 yield 要害字完结的。

当生成器函数在履行进程中遇到 yield 要害字时,它会将当时的履行状况保存下来,并将生成器函数的操控权回来给调用者。这个进程被称为挂起。

挂起后,生成器函数可以等待异步操作的完结,例如等待一个异步请求的响应。一旦异步操作完结,可以经过调用 generator.next(value) 办法康复生成器函数的履行,并将一个值传递给挂起的 yield 表达式。

当生成器函数再次履行到下一个 yield 表达式时,又会被挂起,将当时的履行状况保存下来。这个进程可以重复多次,直到函数履行完毕或遇到 return 句子。

以下是一个简略的示例,演示了生成器函数在异步操作中的挂起和康复:

function* myGenerator() {
  console.log("Start");
  yield 1;
  console.log("After first yield");
  yield 2;
  console.log("After second yield");
  return 3;
}
const generator = myGenerator();
console.log(generator.next()); // { value: 1, done: false }
setTimeout(function () {
  console.log(generator.next()); // { value: 2, done: false }
  console.log(generator.next()); // { value: 3, done: true }
}, 1000);

在上述示例中,生成器函数 myGenerator 中有两个 yield 表达式。在运转到这两个 yield 表达式时,它会别离挂起,并回来相应的值。之后,经过调用 setTimeout 模仿一个异步操作,在 1 秒后再次调用 generator.next() 来康复生成器函数的履行,并传递下一个值。

总结来说,生成器函数经过 yield 要害字完结了在异步操作中的挂起和康复。这使得生成器函数可以以一种流畅而直观的办法处理和操控异步流程。

2. 运用 yield 要害字处理异步操作中的回调函数

生成器函数可以运用 yield 要害字处理异步操作中的回调函数。通常情况下,异步操作的回调函数会被封装在一个 Promise 目标中,用于处理异步操作的成果。

在生成器函数内部,可以运用 yield 要害字暂停函数的履行,并回来一个 Promise 目标。当异步操作完结时,可以经过将成果传递给 Promiseresolve 办法来康复生成器函数的履行。

以下是一个示例,演示了如安在生成器函数中运用 yield 要害字处理异步操作的回调函数:

function asyncOperation(callback) {
  setTimeout(function () {
    const result = "Async Operation Result";
    callback(result);
  }, 1000);
}
function* myGenerator() {
  const result = yield new Promise(function (resolve, reject) {
    asyncOperation(function (data) {
      resolve(data);
    });
  });
  console.log(result);
}
const generator = myGenerator();
const promise = generator.next().value;
promise.then(function (result) {
  generator.next(result);
});

在上述示例中,asyncOperation 是一个模仿的异步操作,它接受一个回调函数作为参数,在异步操作完结后调用该回调函数并传递成果,如下图所示:

myGenerator 生成器函数中,运用 yield 要害字创立一个 Promise 目标,并将异步操作的成果经过回调函数传递给 resolve 办法。生成器函数在履行到这个 yield 表达式时会暂停,并回来这个 Promise 目标。

在主程序中,经过调用 generator.next().value 获取生成器函数的当时 yield 表达式回来的 Promise 目标,并经过 then 办法注册一个回调函数,用于在异步操作完结后持续履行生成器函数。回调函数的参数即为异步操作的成果,经过调用 generator.next(result) 传递给生成器函数,康复函数的履行。

总结来说,生成器函数可以运用 yield 要害字暂停函数的履行,并经过 Promise 目标处理异步操作的回调函数。经过这种办法,可以在异步操作中更直观和流畅地编写、操控代码逻辑。

五. Promise 与生成器的结合运用

Promise 和生成器结合的运用可以帮助咱们更方便地处理异步操作,使代码逻辑更清晰和可读。

异步操作的次序履行是 Promise 和生成器结合的常见运用之一。经过运用生成器函数和yield要害字,咱们可以依照预期的次序履行多个异步操作,并且坚持代码的可读性。

下面是一个示例,演示如何运用 Promise 和生成器结合完结异步操作的次序履行:

function downloadFile(url) {
  return new Promise((resolve, reject) => {
    // 模仿下载文件的异步操作
    setTimeout(() => {
      console.log(`Downloading file from: ${url}`);
      resolve(`File downloaded: ${url}`);
    }, Math.random() * 2000);
  });
}
function* downloadFilesGenerator() {
  const file1 = yield downloadFile("http://example.com/file1.txt");
  console.log(file1);
  const file2 = yield downloadFile("http://example.com/file2.txt");
  console.log(file2);
  const file3 = yield downloadFile("http://example.com/file3.txt");
  console.log(file3);
}
function runGenerator(generator) {
  const iterator = generator();
  function handleResult(result) {
    if (result.done) {
      return result.value;
    }
    return Promise.resolve(result.value)
      .then((res) => handleResult(iterator.next(res)))
      .catch((err) => iterator.throw(err));
  }
  return handleResult(iterator.next());
}
runGenerator(downloadFilesGenerator)
  .then(() => {
    console.log("All files downloaded successfully.");
  })
  .catch((error) => {
    console.error("Error occurred:", error);
  });

在上述示例中,downloadFile 函数模仿了下载文件的异步操作,回来一个 Promise 目标,履行流程如下图所示:

downloadFilesGenerator 生成器函数中,运用 yield 要害字来暂停履行并等待每个文件的下载完结。每个 yield 表达式都会以一个 Promise 目标的形式回来。

runGenerator 函数中,经过递归地处理成果,依照生成器函数中的次序履行异步操作。假如 Promise 目标成功解析,会持续履行下一个异步操作;假如出现错误,则运用 throw 办法抛出异常并在外部的错误处理中捕获。

终究,经过调用 runGenerator(downloadFilesGenerator) 来发动生成器函数的履行,并运用 .then().catch() 办法处理终究的成功和失败情况。

这样,咱们就可以保证异步操作依照次序履行,并在每个异步操作都完结后打印出成果。

六. 结语

ES6 生成器是一种强壮的异步编程工具,供给了一种高雅、灵活且易于理解的办法来处理异步操作的次序和流程。它简化了异步代码的逻辑,使其更易读和维护。经过与 Promise 结合运用,生成器函数可以完结复杂的异步操作操控和错误处理。在开发中,咱们可以充分发挥生成器函数的优势,提高代码的可维护性。

总而言之,ES6 生成器是现代 JavaScript 异步编程中的一把利器,它为咱们带来了愈加高雅和灵活的代码编写办法,使咱们可以更高效地处理异步操作。在适合的场景下,咱们可以充分利用生成器函数的特性,提高代码质量和开发功率,使异步编程变得愈加愉悦和简略。