迭代器——Iterator

迭代器的描述

迭代器(iterator),是确使用户可在容器对象(container,例如链表或数组)上遍访的对象[1][2][3],设计人员使用此接口无需关心容器对象的内存分配的实现细节。其行为很像数据库技术中的光标(cursor),迭代器最早出现在1974年设计的CLU编程语言中。

在JavaScript中,迭代器需要符合迭代器协议(Iterator Protocol),迭代器是一个具体的对象,该对象需要有next()方法。
next()返回一个对象,该对象含有以下两个属性

  • done:该属性是布尔类型, 如果迭代器可以产生序列中的下一个值,则为**false**,如果迭代器不能产生下一个值,则为**true**。当done为true时,value属性是可选的。
  • value:该属性是任何类型,表示当前序列的值。

示例:

  1. let i = 0;
  2. const iterator = {
  3. next() {
  4. if (i++ < 10) {
  5. return { done: false, value: i };
  6. } else {
  7. return { done: true, value: undefined };
  8. }
  9. },
  10. };

image.png

可迭代对象

当一个对象实现了iterable protocol协议时,它就是一个可迭代对象; 这个对象的要求是必须实现 @@iterator方法,在代码中我们使用 Symbol.iterator 访问该属性;当一个对象变成一个可迭代对象的时候,进行某些迭代操作,比如 for...of 操作时,其实就会调用它的 @@iterator 方法。 :::info @@iterator方法返回一个迭代器 ::: 示例:

  1. const names = {
  2. values: ["a", "b", "c", "d", "e"],
  3. [Symbol.iterator]: function () {
  4. let i = 0;
  5. const iterator = {
  6. next: () => {
  7. if (i < this.values.length) {
  8. return { done: false, value: this.values[i++] };
  9. } else {
  10. return { done: true, value: undefined };
  11. }
  12. },
  13. };
  14. return iterator;
  15. },
  16. };
  17. for (const name of names) {
  18. console.log(name);
  19. }

事实上平时创建的很多原生对象已经实现了可迭代协议,会生成一个迭代器对象的。 比如: String、Array、Map、Set、arguments对象、NodeList集合; 可以通过[Symbol.iterator]获取。 image.png

迭代器的应用场景

  • JavaScript中语法:for …of、展开语法(spread syntax)、yield*(后面讲)、解构赋值(Destructuring_assignment);
  • 创建对象时:new Map([Iterable])、new WeakMap([iterable])、new Set([iterable])、new WeakSet([iterable]);
  • 一些方法的调用:Promise.all(iterable)、Promise.race(iterable)、Array.from(iterable);

    迭代器的中断

    迭代器在某些情况下可以中断,比如在遍历过程中使用break,return、throw等或在解构时,没有解构所有的值时。
    迭代器的间断可以被监听:
    需要在返回的迭代器中添加return()方法 ```javascript const names = { values: [“a”, “b”, “c”, “d”, “e”], [Symbol.iterator]: function () { let i = 0; const iterator = {
    next: () => {
      if (i < this.values.length) {
        return { done: false, value: this.values[i++] };
      } else {
        return { done: true };
      }
    },
    return() {
      console.log("迭代器被中断了");
      return { done: true };
    },
    
    }; return iterator; }, };

for (const name of names) { console.log(name); if (name === “c”) { break; } }

:::warning
`**return()**`方法也需要返回包含done的对象
:::
<a name="W5jqk"></a>
## 生成器——Generator
生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数内部的执行、暂停等操作。<br />**生成器**对象是由一个 [generator function](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/function*) 返回的,并且它符合[可迭代协议](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols#iterable)和[迭代器协议](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols#iterator)。
<a name="FP19a"></a>
### 生成器函数的定义
**function*** 这种声明方式function关键字后跟一个星号会定义一个**_生成器函数_ (**_generator function_**)**,它返回一个`Generator`对象。
:::info
*** 生成器(Generator)对象其实是一个特殊的迭代器。**
:::
<a name="gSO6s"></a>
### 生成器的执行
**生成器函数**在执行时能暂停,后面又能从暂停处继续执行。<br />调用一个**生成器函数**并不会马上执行它里面的语句,而是返回一个这个生成器的 **迭代器** **( **[iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#iterator)** )对象**。<br />当这个迭代器的 `next()`方法被首次(后续)调用时,其内的语句会执行到第一个(后续)出现`**yield**`的位置为止,`**yield**`后紧跟迭代器要返回的值。或者如果用的是 [yield*](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/yield*)(多了个星号),则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。<br />`next()`方法返回一个对象,这个对象包含两个属性:value 和 done,value 属性表示本次 `**yield**`表达式的返回值,done 属性为布尔类型,表示生成器后续是否还有 `**yield**`语句,即生成器函数是否已经执行完毕并返回。<br />调用 `next()`方法时,如果传入了参数,那么这个参数会传给**上一条执行的 yield语句左边的变量。**<br />示例:
```javascript
function* foo(a) {
  console.log("statement-1", a);
  const b = yield 1;
  console.log("statement-2", b);
  const c = yield 2;
  console.log("statement-3", c);
}

const fooGenerator = foo("abc");

console.log("ret", fooGenerator.next());            // 执行到 yield 1;
console.log("ret", fooGenerator.next("cba")); // 执行到 yield 2;
console.log("ret", fooGenerator.next("xxx")); // 执行完毕
console.log("ret", fooGenerator.next("xxx"));

image.png

生成器的return方法

**return()** 方法返回给定的值并结束生成器。

return传值后这个生成器函数就会结束,之后调用next不会继续生成值了;

function* foo(a) {
  console.log("statement-1", a);
  const b = yield 1;
  console.log("statement-2", b);
  const c = yield 2;
  console.log("statement-3", c);
  return 999
}

const fooGenerator = foo("abc");

console.log("ret", fooGenerator.next());
console.log("ret", fooGenerator.return("cba"));
console.log("ret", fooGenerator.next("xxx"));
console.log("ret", fooGenerator.next());
console.log("ret", fooGenerator.next());

:::info image.png

  • 如果对已经处于“完成”状态的生成器调用return(value),则生成器将保持在“完成”状态。
  • 如果没有提供参数,则返回对象的value属性为undefined。
  • 如果提供了参数,则参数将被设置为返回对象的value属性的值。 :::

    生成器抛出异常

    **throw()** 方法用来向生成器抛出异常,并恢复生成器的执行,返回带有 donevalue 两个属性的对象。 ```javascript function* foo(a) { console.log(“statement-1”, a); let b = undefined; try { b = yield 1; } catch (error) { console.log(error); } console.log(“statement-2”, b); const c = yield 2; console.log(“statement-3”, c); return 1000; }

const fooGenerator = foo(“abc”);

console.log(“ret”, fooGenerator.next()); console.log(“ret”, fooGenerator.throw(“some error”)); console.log(“ret”, fooGenerator.next()); console.log(“ret”, fooGenerator.return(100));

![image.png](https://cdn.nlark.com/yuque/0/2021/png/21450449/1639117412307-9353c7b6-134b-4b3f-82db-b8d14e0c69c3.png#clientId=ub6f6b52d-ea44-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=270&id=u12af975b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=270&originWidth=832&originalType=binary&ratio=1&rotation=0&showTitle=false&size=27925&status=done&style=none&taskId=ua24bfe14-31e5-4c91-9af2-6015c274e0d&title=&width=832)
<a name="QctXk"></a>
### 使用生成器代替迭代器
```javascript
function* foo() {
  let i = 0;
  while (i < 10) {
    yield i++;
  }
}

const gen = foo();
for (const i of gen) {
  console.log(i);
}

类中方法变成迭代器方法

class GenClass {
  constructor() {
    this.arr = ["a", "b", "c"];
  }

  *[Symbol.iterator]() {
    // 如果用的是 yield*,则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。
    yield* this.arr;
  }
}

async/await

异步代码同步执行——生成器方案

// 模拟网络请求一
function requestToken() {
  return new Promise((resolve) => {
    console.log("requestToken is pending...");
    setTimeout(() => {
      console.log("requestToken resolved.");
      resolve(`ABCDEFG`);
    }, 1500);
  });
}

// 模拟网络请求二
function requestData(token) {
  return new Promise((resolve, reject) => {
    console.log("requestData is pending...");
    setTimeout(() => {
      if (token === "ABCDEFG") {
        const dataList = ["a", "b", "c", "d"];
        resolve(dataList);
      } else {
        reject("error.");
      }
    }, 2500);
  });
}

// 生成器模拟异步函数
function* getData() {
  const token = yield requestToken();
  yield requestData(token);
}

// 执行
const gen = getData();
gen.next().value.then((res) => {
  gen.next(res).value.then((res) => {
    console.log(res);
  });
});

自动执行生成器函数的封装

function execAsyncGenerator(generator, param) {
  function _recursiveGen(param) {
    const result = generator.next(param);
    if (result.done) return result.value;
    result.value.then((res) => {
      _recursiveGen(res);
    });
  }
  _recursiveGen(param);
}

asnyc函数

async函数是使用**async**关键字声明的函数。 async函数是AsyncFunction构造函数的实例, 并且其中允许使用await关键字。asyncawait关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用promise

function resolveAfter2Seconds() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('resolved');
    }, 2000);
  });
}

async function asyncCall() {
  console.log('calling');
  const result = await resolveAfter2Seconds();
  console.log(result);
}

asyncCall();

异步函数的内部代码执行过程和普通的函数是一致的,默认情况下也是会被同步执行。

异步函数与普通函数返回值的区别

  • 普通值:异步函数的返回值会被包裹到Promise.resolve中;
  • Promise:如果异步函数的返回值是Promise,Promise.resolve的状态会由Promise决定;
  • thenable obj:如果异步函数的返回值是一个对象并且实现了thenable,那么会由对象的then方法来决定;

    await关键字

    await操作符用于等待一个Promise 对象。它只能在异步函数 async function 中使用

    await等待值的四种情况

  • 普通值:返回该值本身

  • fulfilled Promise:其回调的resolve函数参数作为 await 表达式的值
  • rejected Promise:await 表达式会把 Promise 的异常原因抛出。
  • thenable obj: 根据对象的then方法调用来决定后续的值。