你是否对JS中的Generator及协程真正理解?

开发 前端
生成器(Generator)是 ES6 中的新语法,相对于之前的异步语法,上手的难度还是比较大的。因此这里我们先来好好熟悉一下 Generator 语法。

[[347310]]

本文转载自微信公众号「前端三元同学」,作者神三元。转载本文请联系前端三元同学公众号。  

生成器(Generator)是 ES6 中的新语法,相对于之前的异步语法,上手的难度还是比较大的。因此这里我们先来好好熟悉一下 Generator 语法。

生成器执行流程

什么是生成器函数?

生成器是一个带星号的"函数"(注意:它并不是真正的函数),可以通过yield关键字暂停执行和恢复执行的

举个例子:

  1. function* gen() { 
  2.   console.log("enter"); 
  3.   let a = yield 1; 
  4.   let b = yield (function () {return 2})(); 
  5.   return 3; 
  6. var g = gen() // 阻塞住,不会执行任何语句 
  7. console.log(typeof g)  // object  看到了吗?不是"function" 
  8.  
  9. console.log(g.next())   
  10. console.log(g.next())   
  11. console.log(g.next())   
  12. console.log(g.next())  
  13.  
  14.  
  15. // enter 
  16. // { value: 1, done: false } 
  17.  
  18. // { value: 2, done: false } 
  19. // { value: 3, done: true } 
  20. // { value: undefined, done: true } 

由此可以看到,生成器的执行有这样几个关键点:

  1. 调用 gen() 后,程序会阻塞住,不会执行任何语句。
  2. 调用 g.next() 后,程序继续执行,直到遇到 yield 程序暂停。
  3. next 方法返回一个对象, 有两个属性: value 和 done。value 为当前 yield 后面的结果,done 表示是否执行完,遇到了return 后,done 会由false变为true。

yield* 语法

当一个生成器要调用另一个生成器时,使用 yield* 就变得十分方便。比如下面的例子:

  1. function* gen1() { 
  2.     yield 1; 
  3.     yield 4; 
  4. function* gen2() { 
  5.     yield 2; 
  6.     yield 3; 

我们想要按照1234的顺序执行,如何来做呢?

在 gen1 中,修改如下:

  1. function* gen1() { 
  2.     yield 1; 
  3.     yield* gen2(); 
  4.     yield 4; 

这样修改之后,之后依次调用next即可。

生成器实现机制——协程

可能你会比较好奇,生成器究竟是如何让函数暂停, 又会如何恢复的呢?接下来我们就来对其中的执行机制——协程一探究竟。

什么是协程?

协程是一种比线程更加轻量级的存在,协程处在线程的环境中,一个线程可以存在多个协程,可以将协程理解为线程中的一个个任务。不像进程和线程,协程并不受操作系统的管理,而是被具体的应用程序代码所控制。

协程的运作过程

那你可能要问了,JS 不是单线程执行的吗,开这么多协程难道可以一起执行吗?

答案是:并不能。一个线程一次只能执行一个协程。比如当前执行 A 协程,另外还有一个 B 协程,如果想要执行 B 的任务,就必须在 A 协程中将JS 线程的控制权转交给 B协程,那么现在 B 执行,A 就相当于处于暂停的状态。

举个具体的例子:

  1. function* A() { 
  2.   console.log("我是A"); 
  3.   yield B(); // A停住,在这里转交线程执行权给B 
  4.   console.log("结束了"); 
  5. function B() { 
  6.   console.log("我是B"); 
  7.   return 100;// 返回,并且将线程执行权还给A 
  8. let gen = A(); 
  9. gen.next(); 
  10. gen.next(); 
  11.  
  12. // 我是A 
  13. // 我是B 
  14. // 结束了 

在这个过程中,A 将执行权交给 B,也就是 A 启动 B,我们也称 A 是 B 的父协程。因此 B 当中最后return 100其实是将 100 传给了父协程。

需要强调的是,对于协程来说,它并不受操作系统的控制,完全由用户自定义切换,因此并没有进程/线程上下文切换的开销,这是高性能的重要原因。

 

责任编辑:武晓燕 来源: 前端三元同学
相关推荐

2018-11-27 09:45:54

2023-11-26 18:35:25

Python编程语言

2023-12-24 12:56:36

协程

2020-11-30 08:25:41

程序员高并发协程

2021-09-16 09:59:13

PythonJavaScript代码

2023-11-17 11:36:59

协程纤程操作系统

2019-06-14 14:58:58

虚拟文件系统Linux

2021-04-28 09:08:23

Kotlin协程代码

2016-12-20 10:55:52

深度学习

2022-05-24 15:09:13

机器人深度学习人工智能

2020-02-24 10:39:55

Python函数线程池

2024-02-05 09:06:25

Python协程Asyncio库

2020-06-19 08:01:48

Kotlin 协程编程

2020-07-07 10:03:27

Android 协程开发

2020-07-09 10:03:07

Android 协程开发

2023-11-28 12:25:02

多线程安全

2022-06-30 09:10:33

NoSQLHBaseRedis

2022-09-06 11:13:16

接口PipelineHandler

2023-10-24 19:37:34

协程Java

2021-12-09 06:41:56

Python协程多并发
点赞
收藏

51CTO技术栈公众号