Iterator, Generator 和 Async

Iterator

Iterator为不同的数据结构提供统一的访问机制,只要它部署Iterator接口,就可以完成遍历操作。

它的流程其实是跟链表的遍历很相似的:创建一个指针对象,指向当前数据结构的起始位置,然后不断的next(),每次返回的是一个对象数据,包含value和done两个字段供访问。

for...of循环实际上会去自动找Iterator接口。

ES6规定,默认的Iterator接口部署在数据结构的Symbol.iterator这个key上,一个数据结构只要具有Symbol.iterator方法,就可以认为是“可遍历的”。

再次提醒,千万注意:Symbol.iterator里面的i是小写!!!

为啥要用Iterator

跟其他语言一样,提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。

原生支持Iterator的类型

原生的表述数据集的类型:ArrayObjectMapSet

而实际上原生支持Iterator的数据结构是:ArrayMapSet

Object默认不支持,其实非要手写支持的话,还不如直接用Map

字符串也支持,因为它类似于Array

手动支持Iterator

Symbol.iterator是一个函数,返回的是一个遍历器,执行next(),不断的返回包含value和done两个字段的对象数据。

类似于Array的Object

例如arguments,对于这种,其实我们可以直接使用ArrayIterator接口:

1
2
3
4
5
6
7
let obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};

如之前所述,能否支持遍历,仅是检查Symbol.iterator这个key而已,所以普通的对象也能支持Iterator,单还不如用Set

普通对象部署Iterator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let obj = {
a: 'a',
b: 'b',
[Symbol.iterator]() {
const self = this;
const keys = Object.keys(self); // 举个例子而已
let i = 0;
return {
next() {
i++;
return {
done: i >= keys.length,
value: self[keys[i]]
};
}
}
}
};

需要注意的是,Iterator的作用是使得数据结构的成员能够按某种次序排列,所以偶尔蛋疼的这么设定,可能仅仅是为了定义一个复杂的访问顺序,但是使用的时候仅仅for...of就好了。

因此也不能强说这么做是不对的。

不过,对于这种,用类不是更好一点么?例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ListIteratorByAge {
constructor(list) {
this.list = list;
}
[Symbol.iterator]() {
const self = this;
const arr = self.list.slice(0).sort((a, b) => a.age >= b.age);
let i = -1;
return {
next() {
i++;
return {
done: i >= arr.length,
value: arr[i]
};
}
}
}
}

注意这里的Symbol.iterator的定义,能不能有更简便的方法呢:

1
2
3
4
5
6
7
8
9
class ListIteratorByAge {
constructor(list) {
this.list = list;
}
[Symbol.iterator]() {
const arr = self.list.slice(0).sort((a, b) => a.age >= b.age);
return arr[Symbol.iterator]();
}
}

再次强调,只是在定义某种次序的访问,没规定要怎么访问!!只是在默认支持的数据结构中按顺序访问了而已。

Iterator的使用

一个经典的硬性使用Iterator的示例:

1
2
3
4
5
6
7
let arr = [1, 4, 2, 10];
let ite = arr[Symbol.iterator]();
let eachResult = ite.next(); // 调用一次访问到第一个元素
while(!eachResult.done) {
// do sth with eachResult.value
eachResult = ite.next();
}

其实直接用for...of更简便:

1
2
3
4
let arr = [1, 4, 2, 10];
for (value of arr) {
// do sth with value
}

几个默认使用 Iterator 的场景

总结自 ECMAScript6 入门

  1. 解构赋值,例如let [x,y] = set
  2. 扩展运算符,例如['a', ...arr, 'd']
  3. yield,例如`yield [2,3,4];`
  4. 由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合都符合条件
    • for…of
    • Array.from()
    • Map(), Set(), WeakMap(), WeakSet()(比如new Map([[‘a’,1],[‘b’,2]]))
    • Promise.all()
    • Promise.race()

Generator

简而言之,结合着上面的Iterator,可以理解Generator实际上是生成了一个可遍历的结果数据集,数据集的元素有哪些,由yieldreturn定义,函数执行的结果就是数据集的Iterator

例如:

1
2
3
4
5
6
function* testGenerator() {
yield 1;
yield 2;
return 3;
}
let ite = testGenerator();

那么不断执行ite.next()并以!ite.next().done为判断条件的执行结果为:1,2,3

next可以传入值,作为执行时上一个yield的执行返回结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function* counterGen() {
let i = 0;
while(1) {
let signal = yield i++;
// console.log('now is ' + i);
if (signal) {
i = 0;
}
}
}
let counter = counterGen();
counter.next(); // {value: 0, done: false}
...
counter.next(true); // {value: 0, done: false} 重置了

需要注意的是,在执行next(true)时,counterGen 中那个注释掉的console语句中,i其实是6,后面置的0。
执行的最终结果中,value是0。
也就是说,yield的结果其实是可以被后面的语句给改掉的,或者可以将例子中while语句{}包起来的部分认为是一个函数,最后返回i是最终的结果。
也可以这么理解:执行到下一个yield时,才返回了之前的结果,大概、或许是这样吧……

总结一小下:

  1. 定义时,function 后面跟着一个*
  2. 内部用yield定义一个可被遍历到的元素(状态)
  3. return也会被认为是一个可被遍历到的元素(状态),如果没有return或没跟返回值,value会是undefined
  4. 状态会被后面的同步代码所改变,直到下一次yield之前都可以

不得不说的 yield *

本来不想说这个,感觉要变成基础知识集了,不过研究中间件偏偏遇到了,不得不说……

yield*最大的作用,就是将Generator嵌在另一个Generator的内部执行。

1
2
3
4
5
6
7
8
function *a() {
console.log(1);
b();
console.log(3);
}
function *b() {
console.log(2);
}

执行结果是13,是的,2不会被输出,但是如果换成:

1
yield *b();

就等同于:

1
2
3
4
5
function *a() {
console.log(1);
console.log(2);
console.log(3);
}

输出就是123就好了。

而这也是koa2的中间件核心实现机制,笑话一下自己搞出来的遍历行为,然后傻在next的调用上了,囧~~

可以查看

Generator更多的用法

建议查看 ECMAScript6 入门

Async

语法糖,是对Generator的改进。

返回的结果是一个Promise

错误机制

只需注意几点就行了。

  1. 返回结果是Promise,所以按Promise的错误处理就行了
  2. async内部的await指定的状态,是同步的,全都成功整体状态才会成功,任何一个失败了,整体也就失败了(后面的不执行了),除非用try…catch包起来或者用.catch处理一下

for await of

1
2
3
4
let body = '';
for await(const data of req) body += data;
const parsed = JSON.parse(body);
console.log('got', parsed);