JS异步

generator iterator

Iterator

迭代器(Iterator)是一种接口,或者说是一种机制。它能为各种不同的数据结构提供统一的访问机制,任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。主要的作用是为了为各种数据结构提供一个统一的简便的访问接口,使数据结构的成员能够按某种次序排列。

Iterator本质上是一个指针对象,实现过程如下:

  1. 创建一个指针对象,指向当前数据结构的起始位置。
  2. 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
  3. 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
  4. 不断调用指针对象的next方法,直到它指向数据结构的结束位置。

知识点

  1. forEach 并不能保证异步代码按顺序执行,但可以利用for...of解决。for...of并不像forEach那么简单粗暴的方式去遍历执行,而是采用一种特别的手段——迭代器去遍历。
  2. 生成器(Generator)是 ES6 中的新语法,生成器是一个带星号的"函数"(注意:它并不是真正的函数),可以通过yield关键字暂停执行和恢复执行的,生成器实现机制——协程
  3. 协程是一种比线程更加轻量级的存在,协程处在线程的环境中,一个线程可以存在多个协程,可以将协程理解为线程中的一个个任务。不像进程和线程,协程并不受操作系统的管理,而是被具体的应用程序代码所控制。一个线程一次只能执行一个协程。
  4. 执行生成器会得到一个具有next方法的迭代器对象(生成器本身就是一个迭代器。)

async await

  1. async/await利用协程和Promise实现了同步方式编写异步代码的效果,其中Generator是对协程的一种实现
  2. async函数就是generator函数的语法糖。
  3. async函数,就是将generator函数的*换成async,将yield替换成await。

async函数对generator的改进

  1. 内置执行器,不需要使用next()手动执行。
  2. await命令后面可以是Promise对象或原始类型的值,yield命令后面只能是Thunk函数或Promise对象。
  3. 返回值是Promise。返回非Promise时,async函数会把它包装成Promise返回。(Promise.resolve(value))

原生具有[Symbol.iterator]属性数据类型为可迭代数据类型。如字符串、数组、类数组(如arguments、NodeList)、Set和Map

let arr = [4, 2, 1];
let iterator = arr[Symbol.iterator]();  // 这就是迭代器
1
2

如何让一个普通对象变成可迭代对象?

添加Symbol.iterator属性(方法) 该方法会返回一个具有next方法的对象 而这个next方法会返回具有value和done两个属性的对象

const myIterator = {
    value: 0,
    [Symbol.iterator]() {
        return {
            next: () => {
                const done = this.value > 2;
                return {
                    value: done ? undefined : this.value++,
                    done
                };
            }
        };
    }
};
const iter = myIterator[Symbol.iterator]();
iter.next(); // { value: 0, done: false }
iter.next(); // { value: 1, done: false }
iter.next(); // { value: 2, done: false }
iter.next(); // { value: undefined, done: true }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Generator与普通函数的区别

  1. 在进行函数声明的时,function关键字与函数名之间有一个星号。同时不能使用箭头函数进行声明,否则会报错。
  2. Generator 函数的返回值与普通函数不同,而是会返回一个迭代器对象,该对象可以依次对 Generator 函数内部的每一个状态进行迭代。
  3. 函数体内部使用yield表达式,定义不同的内部状态。
  4. Generator 函数不能使用new关键字,否则会报错。

为什么要用协程

解决线程阻塞状态和可运行状态之间的切换和线程上下文切换的性能缺陷问题。

直白地讲,进程就是应用程序的启动实例。比如我们运行一个游戏,打开一个软件,就是开启了一个进程。进程拥有代码和打开的文件资源、数据资源、独立的内存空间。

线程从属于进程,是程序的实际执行者。一个进程至少包含一个主线程,也可以有更多的子线程。线程拥有自己的栈空间。

关于协程

协程(Coroutines)是一种比线程更加轻量级的存在,正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程。

协程不是被操作系统内核所管理的,而是完全由程序所控制,也就是在用户态执行。这样带来的好处是性能大幅度的提升,因为不会像线程切换那样消耗资源。

协程不是进程也不是线程,而是一个特殊的函数,这个函数可以在某个地方挂起,并且可以重新在挂起处外继续运行。所以说,协程与进程、线程相比并不是一个维度的概念。

一个进程可以包含多个线程,一个线程也可以包含多个协程。简单来说,一个线程内可以由多个这样的特殊函数在运行,但是有一点必须明确的是,一个线程的多个协程的运行是串行的。如果是多核CPU,多个进程或一个进程内的多个线程是可以并行运行的,但是一个线程内协程却绝对是串行的,无论CPU有多少个核。毕竟协程虽然是一个特殊的函数,但仍然是一个函数。一个线程内可以运行多个函数,但这些函数都是串行运行的。当一个协程运行时,其它协程必须挂起。  协程与进程一样,切换是存在上下文切换问题的。

但协程的切换过程只有用户态,即没有陷入内核态,因此切换效率高。