iterator 迭代器
let arr = [1,2,3,4];
console.log(arr);
arr在原型上部署了一个迭代器接口Symbol(Symbol.iterator): ƒ values()
构造器(构造器属性):方法
通过arr[Symbol.iterator]可以拿到对应的方法
let iter = arr[Symbol.iterator]
console.log(iter); //ƒ values() { [native code] }
let iter = arr[Symbol.iterator]();
console.log(iter);
执行方法可以看见原型里有next方法
console.log(iter.next()); //{value: 1, done: false}
执行返回对象,value代表读取的值,false代表还没有执行完
let arr = [1,2,3,4];
let iter = arr[Symbol.iterator]();
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
连续执行六次,可以看到以下结果
迭代器:读取数据结构的一种方式,有序的,连续的,基于拉取的一种消耗数据的组织方式(抽取过了就不再访问了)
封装迭代器
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{ value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
}
}
let iter = makeIterator([5,2,3,4]);
但是这种方式一直next,有点累赘,ES6中借鉴了c++,java,c#,python,有了for...of...循环 -> 迭代
for...of...调用的就是iterator接口
let arr = [1,2,3,4]
for(let i of arr) {
console.log(i); //1,2,3,4
}
for..in..拿的是下标,遍历对象;for..of..直接拿的值,迭代部署过迭代器接口的数据类型(Array、Map、weakMap、weakSet、类数组[arguments、nodeList]、typeArray),除了对象(对象是无序的)
如果想让对象迭代的话,就要部署迭代器接口
let obj = {
start: [1,2,3,4],
end: [5,6,7],
[Symbol.iterator](){
let index = 0,
arr = [...this.start, ...this.end],
len = arr.length;
return {
next(){
if(index < len) {
return {
value: arr[index++],
done: false
}
}else{
return {
value: undefioned,
done: true
}
}
}
}
}
}
for(let i of obj){
console.log(i);
}
var arr = [6,7,5,1];
var iter = arr[Symbol.iterator]();
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
for(let i of arr){ //两种方式都用的话,for of有可能会失效
console.log(i);
}
...在对象部署迭代器接口的情况下是可以用的,没有部署拓展对象会报错
let arr = [...obj];
console.log(arr); //[1,2,3,4,5,6,7]
Map结构还是有顺序的,所以可以迭代,通过for of方法拿到键值对
let map = new Map([['a', 1], ['b', 2]]);
for(let [key, value] of map) {
console.log(key, value);
}
如果对象想要和Map结构一样
let obj = {
a: 1,
b: 2,
[Symbol.iterator]() {
let nextIdx = 0;
// 对象转换为Map
let map = new Map();
// Object.entries()方法返回的是数组,所以可以用for of迭代
for(let [key, value] of Object.entries(this)) {
map.set(key, value);
}
// 迭代器对象
// let mapEntries = map.entries();
// Map转换为数组,直接拓展map也可以,这样语义化更明显一点
let mapEntries = [...map.entries()];
return {
next() {
return nextIdx < mapEntries.length ?
{value: mapEntries[nextIdx ++], done: false} :
{value: undefined, done: true}
}
}
// console.log(map)
}
}
for(let i of obj) {
console.log(i);
}
但是这种方法其实没什么必要,直接用Map不就完了吗 部署迭代器的时候还可以部署其他方法,上面的只是必选项而已
return {
next() {...},
return() {
console.log(1);
return {c: 3}
}
}
for(let i of obj) {
console.log(i);
break;
}
return必须要返回一个对象,并且return方法会在迭代出错或者break的时候执行
内部迭代器和外部迭代器 前端是不区分内部迭代器和外部迭代器的(调用方式不一样),但是其他语言有,稍微了解一下
内部迭代器:系统内部定义好的迭代规则,在进行调用的时候,可以一次性拿到所有的遍历元素(for的一系列方法) 外部迭代器:自己部署迭代器接口,一次抽取一次数据
默认调用iterator接口的场合
- 拓展运算符...
- for of
- Array.from()
- map/set
- Promise.all( [] ) / Promise.race()
- yield
generator
生成器:返回迭代器对象
语法:在function和函数名之间加一个*
哪怕什么都不写,返回的也是一个迭代器对象
function *test() {}
let iter = test();
console.log(iter);
yield产出
特征:
一 、返回值是迭代器对象
二 、yield产出不同的内部值,暂停函数运行
迭代器对象一定要和yield(产出)结合使用,只能出现在生成器当中,在普通函数中会报错
yield只有在next调用的时候才会产出,同时会暂停函数运行,因为迭代器是一次次抽取的
function *test() {
yield 'a';
console.log(1);
yield 'b';
yield 'c';
return 'd';
}
let iter = test();
console.log(iter.next()); //{value: "a", done: false} 不会打印1,到第一个yield就停止了,到下一个next才会打印
console.log(iter.next()); // 1 {value: "b", done: false}
console.log(iter.next()); //{value: "c", done: false}
console.log(iter.next()); //{value: "d", done: true}
console.log(iter.next()); //{value: undefined, done: true}
yield和return的区别:
都会产出值,return不会被记录到iterator中:yield产出有值,done就是false;yield产出没有值,done就是false,哪怕最后return了一个d出来
yield有记忆的功能,本质是暂停;return是终止函数运行
三 、yield本身并不产出值,undefined
function *test() {
let val = yield 'a';
console.log(val);
yield 'b';
yield 'c';
return 'd';
}
let iter = test();
console.log(iter.next()); //{value: "a", done: false} undefined
console.log(iter.next()); //{value: "b", done: false}
想要yield本身产出值,就传递参数
function *test() {
let value1 = yield 1;
console.log(value1);
let value2 = yield 2;
console.log(value2);
let value3 = yield 3;
console.log(value3);
}
let iter = test();
console.log(iter.next());
console.log(iter.next('one'));
console.log(iter.next('two'));
console.log(iter.next('three'));
第二个next才可以得到第一个yield的赋值,第一个next没必要传值
利用yield进行一系列操作 yield如果直接嵌套在表达式当中,会报错,要加个括号(变成单独的表达式)才行
例1:独立存在
function *test() {
yield; //直接这样也行,返回的value是undefined
}
例2:
function *test() {
console.log('hello ' + (yield 123));
}
let iter = test();
console.log(iter.next());
console.log(iter.next());
{value: 123, done: false}
hello undefined
{value: undefined, done: true}
例3:作为参数传递
function *test() {
foo(yield 'a', yield 'b');
}
function foo(a, b) {
console.log(a, b);
}
let iter = test();
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
{value: "a", done: false} //yield 'a'执行
{value: "b", done: false} //yield 'b'执行
undefined undefined //foo执行
{value: undefined, done: true}
yield产出的值是迭代器对象,就可以用for of遍历
function *test() {
yield 1;
yield 2;
yield 3;
yield 4;
return 5;
}
for (let i of test()) {
console.log(i); // 1 2 3 4 没有5
}
进行iterator接口代码的优化
let obj = {
start: [1,2,3,4],
end: [5,6,7],
[Symbol.iterator]: function *(){
let index = 0,
arr = [...this.start, ...this.end],
len = arr.length;
while(index < len) {
yield arr[index++];
}
}
}
用generator和yield,替代模拟返回{value: ... , done: ...}对象的代码。因为生成器函数会自动返回iterator对象,只需要用yield产出值即可,不需要再进行条件判断了。
解决回调地狱的问题
function promisify(func) {
return function(...args) {
return new Promise( (resolve, reject) => {
func(...args, (err, data) => {
if (data) {
resolve(data);
} else {
reject(err);
}
}
})
}
}
let readFile = promisify(fs.readFile);
// readFile('./name.txt', 'utf-8') //之前的做法
// .then(data => readFile(data, 'utf-8'))
// .then(data => readFile(data, 'utf-8'))
// .then(data => console.log(data));
function * read() {
let value1 = yield readFile('./name.txt', 'utf-8');
let value2 = yield readFile(value1, 'utf-8');
let value3 = yield readFile(value2, 'utf-8');
console.log(value3);
}
let iter = read();
// let a = iter.next();
// 用解构赋值来做,调用next就相当于抽取一次iterator
// let {value: value, done: done} = iter.next();
// 简写
let {value, done} = iter.next();
// value是readFile()函数执行之后的结果,也就是Promisify之后的结果( promise对象,成功就resolve(data))=> 可链式调用
value.then( (val1) => {
// 这里的val就是第一次readFile('./name.txt', 'utf-8')的结果
// 传值val1得到第二次读取值成功的结果
let {value, done} = iter.next(val1);
value.then( (val2) => {
let {value, done} = iter.next(val2);
value.then( (val3) => {
console.log(val3);
})
})
})
但是这么写还不如之前的代码,所以需要进行代码优化
function Co(iter) {
return new Promise( (resolve, reject) => {
// 之前频繁的调用了next
let next = (data) => {
let {value, done} = iter.next(data);
// 递归的出口,done为true,结束
if (done) {
resolve(value);
} else {
value.then( (val) => {
next(val);
})
}
}
// 首先执行一遍,不要参数,启动递归
next();
})
}
let promise = Co(read());
promise.then( (val) => {
// 一步到位
console.log(val);
})
Co是一个模块,可以下载安装
return
function *get() {
yield 1;
//return 10
yield 2;
yield 3;
}
let g = get();
console.log(g.next());
console.log(g.return(10));
console.log(g.next());
console.log(g.next());
console.log(g.next());
{value: 1, done: false}
{value: 10, done: true}
{value: undefined, done: true}
{value: undefined, done: true}
{value: undefined, done: true}
return会结束遍历器的迭代,和在函数里return一样,相当于显示调用return
return也相当于yield一次,产出值,但是产出的done是true,不在迭代器当中
throw
unction *get() {
try {
yield 1;
} catch(e) {
console.log('生成器内部异常:'+ e);
}
yield 2;
}
let g = get();
console.log(g.next());
console.log(g.throw('a'));
console.log(g.next());
结果:Uncaught a
throw在第一次next之前是不能捕获到异常的,因为第一次next是yield产出值,next(产出值)之后才会在try的代码区间中。两行掉个顺序就可以正常捕获
结果:
{value: 1, done: false}
生成器内部异常:a
{value: 2, done: false}{value: undefined, done: true}
throw不仅可以抛出异常,也相当于一次next迭代,所以打印异常之后还会产出一次值(拿到yield 2的值)
一般的try catch是不可以捕获到异步代码的异常的。但是在生成器中可以
let fs = require('fs');
let util = require('util');
let co = require('co');
let readFile = util.promisify(fs,readFile);
function *read() {
try {
let value1 = yield readFile('./name.tx', 'utf-8');
let value2 = yield readFile(value1, 'utf-8');
let value1 = yield readFile(value2, 'utf-8');
} catch(e) {
console.log('生成器内部异常:'+ e);
}
return value3;
}
let promise = co(read());
promise.then( (val) => {
console.log(val);
})
async
await就相当于co,co被封装到await内部,作用就是等待异步函数的执行,然后将结果赋值(本质上是generator[语法糖],底层的操作和之前一样)
let fs = require('fs');
let util = require('util');
let readFile = util.promisify(fs,readFile);
async function read() {
let value1 = await readFile('./name.tx', 'utf-8'); //传错值,这个函数的Promise对象就有了reject状态,然后传递状态
let value2 = await readFile(value1, 'utf-8');
let value3 = await readFile(value2, 'utf-8');
console.log(value1 ); //打印出来的结果是字符串
return value1; //相当于return Promise.resolve(value1)
}
let promise = read();
promise.then( (val) => {
console.log(val); //打印出来的结果是promise对象,产出的时候会直接转成promise对象
}, (e) => { //根据返回的promise的状态(reject)进行报错
console.log(e);
})
根据返回的promise的状态进行相应的回调
特点:
- 有内置的执行器:co
- 语义好太多了
- 更广的适用性:原本的co是一个模块,也就是函数,必须传入一个promise对象,yield后面一定要是promise对象;await后面不仅可以是promise对象
- 返回值一定是promise对象:不管await之后的是什么值,最后return都会通过Promise.resolve包装成promise对象 捕获错误:通过Promise的reject的状态调用失败的回调或者try catch都可以,精准捕获还是要用catch,reject只要其中有一个Promise错误就会捕获 通过reject捕获的错误和通过catch捕获的错误是不一样的:
reject是通过传递状态,触发相应的失败的回调函数,出错的地方之后的代码不会继续执行 catch是捕获到错误,立即处理了 如果异步操作没有依赖关系(不是回调地狱),即使有一个出错,还想拿到剩下的异步操作的结果
let f = Promise.all([
readFile('./name.txt', 'utf-8'),
readFile('./name.txt', 'utf-8'),
readFile('./name.txt', 'utf-8')
])
async function readAll() {
let value1, value2, value3;
let res = new Set();
try {
value1 = await readFile('./name.txt', 'utf-8');
} catch {
console.log(e);
}
try {
value2 = await readFile('./number.txt', 'utf-8');
} catch {
console.log(e);
}
try {
value3 = await readFile('./data.txt', 'utf-8');
} catch {
console.log(e);
}
res.add(value1);
res.add(value2);
res.add(value3);
return res;
}
readAll().then( (val) => {
console.log(val);
})
但是一般不怎么做,还是用Promise.all