迭代器和可迭代协议
redux-saga是建立在ES6的生成器基础上的,要熟练的使用saga,必须理解生成器。
要理解生成器,必须先理解迭代器和可迭代协议。
迭代
类似于遍历
遍历:有多个数据组成的集合数据结构(map、set、array等其他类数组),需要从该结构中依次取出数据进行某种处理,有特定的数据结构。
迭代:按照某种逻辑,依次取出下一个数据进行处理。
迭代器 iterator
JS语言规定,如果一个对象具有next方法,并且next方法满足一定的约束,则该对象是一个迭代器(iterator)。
next方法的约束:该方法必须返回一个对象,该对象至少具有两个属性:
- value:any类型,下一个数据的值,如果done属性为true,通常,会将value设置为undefined
- done:bool类型,是否已经迭代完成
通过迭代器的next方法,可以依次取出数据,并可以根据返回的done属性,判定是否迭代结束。
var iterator = {
total:5,
index:1,
next(){
var obj = {
value:this.index > this.total ? undefined:this.index,
done: this.index > this.total
}
this.index++
return obj;
}
}
迭代器创建函数 iterator creator
它是指一个函数,调用该函数后,返回一个迭代器,则该函数称之为迭代器创建函数,可以简称为迭代器函数。
function createIterator(){
let total = 3,
index = 1;
return {
next(){
var obj = {
value:index > total ? undefined:index,
done: index > total
}
index++
return obj;
}
}
}
//用户迭代数组的迭代器
function createArrayIterator(arr) {
var i = 0; //下标从0开始迭代
return {
next() {
return {
value: arr[i++],
done: i > arr.length
}
}
}
}
var iterator = createArrayIterator([3, 6, 7, 2, 1, 3]);
可迭代协议
ES6中出现了for-of循环,该循环就是用于迭代某个对象的,因此,for-of循环要求对象必须是可迭代的(对象必须满足可迭代协议)
可迭代协议是用于约束一个对象的,如果一个对象满足下面的规范,则该对象满足可迭代协议,也称之为该对象是可以被迭代的。
可迭代协议的约束如下:
- 对象必须有一个知名符号属性(Symbol.iterator)
- 该属性必须是一个无参的迭代器创建函数
for-of循环的原理
调用对象的[Symbol.iterator]方法,得到一个迭代器。不断调用next方法,只有返回的done为false,则将返回的value传递给变量,然后进入循环体执行一次。
手写实现for-of
const obj = {
[Symbol.iterator]:function createIterator(){
let total = 3,
index = 1;
return {
next(){
var obj = {
value:index > total ? undefined:index,
done: index > total
}
index++
return obj;
}
}
}
}
const iterator = obj[Symbol.iterator]();
let next = iterator.next()
while(!next.done){
//done属性为false进入循环
const item = next.value;
console.log(item); //执行循环体
next = iterator.next();
}
//同上功能类似
for (const item of obj) {
console.log(item)
}
生成器 generator
generator
生成器:由构造函数Generator创建的对象,该对象既是一个迭代器,同时,又是一个可迭代对象(满足可迭代协议的对象)
//伪代码
var generator = new Generator();
generator.next();//它具有next方法
var iterator = generator[Symbol.iterator];//它也是一个可迭代对象
for(const item of generator){
//由于它是一个可迭代对象,因此也可以使用for of循环
}
注意:Generator构造函数,不提供给开发者使用,仅作为JS引擎内部使用
generator function
生成器函数(生成器创建函数):该函数用于创建一个生成器。
ES6新增了一个特殊的函数,叫做生成器函数,只要在函数名与function关键字之间加上一个*号,则该函数会自动返回一个生成器
生成器函数的特点:
- 调用生成器函数,会返回一个生成器,而不是执行函数体(因为,生成器函数的函数体执行,收到生成器控制)
- 每当调用了生成器的next方法,生成器的函数体会从上一次yield的位置(或开始位置)运行到下一个yield
- yield关键字只能在生成器内部使用,不可以在普通函数内部使用
- 它表示暂停,并返回一个当前迭代的数据
- 如果没有下一个yield,到了函数结束,则生成器的next方法得到的结果中的done为true
- yield关键字后面的表达式返回的数据,会作为当前迭代的数据
- 生成器函数的返回值,会作为迭代结束时的value
- 但是,如果在结束过后,仍然反复调用next,则value为undefined
- 生成器调用next的时候,可以传递参数,该参数会作为生成器函数体上一次yield表达式的值。
- 生成器第一次调用next函数时,传递参数没有任何意义
- 生成器带有一个throw方法,该方法与next的效果相同,唯一的区别在于:
- next方法传递的参数会被返回成一个正常值
- throw方法传递的参数是一个错误对象,会导致生成器函数内部发生一个错误。
- 生成器带有一个return方法,该方法会直接结束生成器函数
- 若需要在生成器内部调用其他生成器,注意:如果直接调用,得到的是一个生成器,如果加入*号调用,则进入其生成器内部执行。如果是
yield* 函数()
调用生成器函数,则该函数的返回结果,为该表达式的结果
//模仿异步获取数据
function asyncGetData(){
return new Promise(resolve=>{
setTimeout(() => {
resolve("异步数据获取成功")
}, 3000);
})
}
//生成器函数
function* createGenerator(){
console.log("开始");
let res = yield asyncGetData();
console.log("第一次:",res);
res = yield asyncGetData();
console.log("第二次:",res);
res = yield 1;
console.log("结束",res);
}
//参数是一个生成器函数
function run(func){
const generator = func();
next()
function next(nextVal){
const result = generator.next(nextVal);
if(result.done){
//迭代结束
return;
}
//简单判断是不是promise,更细的判断可以用isPromise库
if(typeof result.value.then === "function"){
result.value.then(data=>next(data));
}else{
next(result.value);
}
}
}
run(createGenerator)