JavaScript中的生成器函数以及yield
生成器函数与yield语句
生成器函数是 JavaScript 中一种特殊类型的函数,它能够逐步产生多个值,而不是一次性返回全部结果。与普通函数不同,生成器函数可以暂停其执行过程,并在需要时恢复执行。
生成器函数使用 function*
声明语法来定义,而不是普通函数的 function
关键字。在生成器函数中,使用 yield
语句来指示函数的执行流程,yield
语句可以将函数的执行暂停,并返回一个值。
下面是一个简单的生成器函数的示例,它用于产生数字序列:
function* numberSequence() {
let i = 1;
while (true) {
yield i++;
}
}
const generator = numberSequence();
console.log(generator.next().value); // 1
console.log(generator.next().value); // 2
console.log(generator.next().value); // 3
console.log(generator.next().value); // 4
在这个示例中,我们定义了一个生成器函数 numberSequence()
,它在一个无限循环中使用 yield
语句逐步产生数字序列。我们创建了一个生成器对象 generator
并对其调用了 next()
方法,每次调用 next()
方法都会从上一次 yield
语句处开始执行,并在下一个 yield
语句处暂停。
生成器函数有以下几个特点:
- 生成器函数使用
function*
声明语法来定义,而不是普通函数的function
关键字。 - 生成器函数内部可以使用
yield
语句来暂停函数的执行,并返回一个值。 - 生成器函数可以多次返回值,每次返回一个值。
- 生成器函数可以使用
return
语句来结束函数的执行,并返回一个最终值。 - 生成器函数可以使用
throw
语句来抛出一个异常。 - 生成器函数可以通过
for...of
循环来迭代产生的值。
使用场景
生成器函数可以在以下场景中使用:
- 异步编程:使用生成器函数可以方便地实现异步编程模式,如协程和生成器模式。
- 处理大量数据:使用生成器函数可以避免一次性将大量数据全部加载到内存中,而是逐步从外部数据源获取数据。
- 状态机:使用生成器函数可以轻松地实现状态机,例如有限状态机(FSM)或下推自动机(PDA)。
总之,生成器函数是 JavaScript 中一种非常有用的函数类型,它可以使程序更加灵活、可扩展和易于理解。它使得函数能够产生多个值,并可以控制函数的执行过程,让函数更具有交互性和可控性。
使用场景实现代码
- 异步编程:使用生成器函数实现协程
生成器函数通常用于异步编程,特别是实现协程(coroutine)时非常有用。在异步编程中,程序可以在等待某些操作完成时执行其他任务,以提高性能和响应能力。
以下是一个简单的例子,演示了如何使用生成器函数来实现协程,它可以在等待异步操作完成时暂停执行,并在操作完成时恢复执行:
function* myCoroutine() {
const result1 = yield asyncOperation1();
const result2 = yield asyncOperation2(result1);
const result3 = yield asyncOperation3(result2);
return result3;
}
const generator = myCoroutine();
generator.next().value.then(result1 => {
return generator.next(result1).value;
}).then(result2 => {
return generator.next(result2).value;
}).then(result3 => {
console.log(result3);
});
在这个示例中,我们定义了一个协程 myCoroutine()
,它执行了一系列异步操作,并在每次等待异步操作完成时暂停执行,并使用 yield
语句返回异步操作的结果。我们创建了一个生成器对象 generator
并对其调用了 next()
方法,每次调用 next()
方法都会从上一次 yield
语句处开始执行,并在下一个 yield
语句处暂停。我们使用 Promise 的 then()
方法来处理异步操作的结果,并将结果传递给下一个 yield
语句。
使用生成器函数实现协程的好处在于,它可以使异步编程更加容易和直观。使用生成器函数,我们可以将异步操作组织成顺序代码,而不需要使用回调函数或 Promise 链式调用等复杂的编程模式。
- 处理大量数据:逐步读取大型数据
生成器函数也非常适合处理大量数据。使用生成器函数,我们可以逐步从外部数据源获取数据,而不是一次性将大量数据全部加载到内存中。这样可以提高程序的效率和可扩展性。
以下是一个简单的例子,演示了如何使用生成器函数从文件中逐行读取数据:
function* readLines(file) {
const stream = fs.createReadStream(file);
const reader = readline.createInterface({
input: stream,
crlfDelay: Infinity
});
for await (const line of reader) {
yield line;
}
}
const lineGenerator = readLines('large-file.txt');
for (let line of lineGenerator) {
console.log(line);
}
在这个示例中,我们定义了一个生成器函数 readLines()
,它从文件中逐行读取数据,并使用 yield
语句将每行数据返回。我们创建了一个生成器对象 lineGenerator
并对其进行迭代,每次迭代都会获取一个新的数据行并进行处理。这种方式可以避免一次性将大量数据全部加载到内内存中,而是逐步从外部数据源获取数据,从而提高程序的效率和可扩展性。
- 无限序列:生成无限序列
生成器函数还可以用于生成无限序列。例如,我们可以使用生成器函数来生成斐波那契数列(Fibonacci sequence)
:
function* fibonacci() {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const generator = fibonacci();
for (let i = 0; i < 10; i++) {
console.log(generator.next().value);
}
在这个示例中,我们定义了一个生成器函数 fibonacci()
,它使用一个 while
循环不断生成斐波那契数列,并使用 yield
语句将每个数返回。我们创建了一个生成器对象 generator
并对其进行迭代,每次迭代都会获取一个新的数并进行处理。由于生成器函数可以无限生成序列,因此我们需要在迭代时指定要生成的元素数量。
使用生成器函数生成无限序列的好处在于,它可以使我们更加方便地生成任意长度的序列,并可以避免一次性生成大量的数据。
yield和async await对比
yield
和 async/await
都是 JavaScript 中的异步编程技术,它们都可以用于协程(coroutine)的实现,但是它们的使用方式和实现方式略有不同。
yield
用于定义生成器函数中的迭代器对象,通过 yield
语句可以将控制权交回给生成器的调用者,以便生成器可以在需要时暂停执行。生成器可以在需要时恢复执行,并继续执行其余的代码。yield
主要用于生成器函数中的同步代码,它可以将同步代码转换为异步代码,从而实现协程。
async/await
是 ECMAScript 2017 标准中引入的一种异步编程模式,它基于 Promise 对象并使用关键字 async
和 await
来进行异步代码的编写和执行。async
关键字用于定义一个异步函数,它会返回一个 Promise 对象,而 await
关键字用于等待一个异步操作的完成,从而避免了回调函数的嵌套和代码可读性的问题。
相比于 yield
,async/await
更加直观、易于理解和使用。使用 async/await
可以使代码更加简洁、清晰,并且可以更好地处理异步代码的错误和异常。但是,与 yield
相比,async/await
的实现更加复杂,并且需要在编译时将异步代码转换为 Promise 对象,因此在一些旧版浏览器中可能不被支持。
总体来说,yield
和 async/await
都是实现异步编程的有效方法,它们各有优劣,可以根据具体情况选择使用。如果需要实现较为复杂的异步操作,建议使用 async/await
;如果需要实现一些简单的异步操作,可以使用 yield
进行实现。
转载自:https://juejin.cn/post/7207250137466552375