2018-3-6 作者: 张子阳 分类: Web前端
之前在React项目中,遇到异步请求,都是通过redux-thunk来处理,但使用这种方式,action就变得不那么纯净了。当前新的趋势是使用redux-saga来处理side effects(副效应)。在redux-saga中,重度使用了generator函数的概念,这篇文章先就Generator函数做一个小结。
与普通函数的声明不同,Generator函数需要在function关键字后面加星号*。
function* generator(){
console.log("a")
}
执行Generator函数并不会运行函数体,而是返回一个迭代器iterator对象。
let iterator = generator(); // 并不会输出a
如果想要运行generator函数,则需要在迭代器上执行next()方法。
function* generator(){
console.log("a")
return "a";
}
let iterator = generator();
let state = iterator.next(); // 输出a
console.log(state); // 输出 [object Object] { done: true, value: "a" }
next()方法返回了一个对象,该对象有两个参数,done参数表明了generator函数是否执行完毕,value则为函数的返回值。
目前看上去Generator函数好像并没有什么用,实际上,它可以结合yield关键字,从而实现函数的分段执行。
function* generator(){
console.log("a1")
yield "a2";
console.log("b1")
yield "b2";
console.log("c1")
return "c2";
}
let iterator = generator();
let state = iterator.next(); // 输出a1
console.log(state); // 输出 [object Object] { done: false, value: "a2" }
state = iterator.next(); // 输出b1
console.log(state); // 输出 [object Object] { done: false, value: "b2" }
state = iterator.next(); // 输出b3
console.log(state); // 输出 [object Object] { done: true, value: "b3" }
state = iterator.next(); // 无输出
console.log(state); // 输出 [object Object] { done: true, value: undefined }
执行上面的代码,可以看到,函数每运行到yield的位置就暂停了,直到下一次执行next()。
从上面的例子,可以看到,通过使用yield和return,可以获取Generator函数每段执行的返回值。那么如何向函数中传入值?可以通过函数的参数和next()方法。
function* generator(p){
console.log("1: " + p)
p = yield "X";
console.log("2: " + p)
p = yield "Y";
console.log("3: " + p)
}
let iterator = generator("A");
let state = iterator.next("B"); // 输出 1: A
console.log(state); // 输出 [object Object] { done: false, value: "X" }
state = iterator.next("C"); // 输出 2: C
console.log(state); // 输出 [object Object] { done: false, value: "Y" }
state = iterator.next("D"); // 输出 3: D
console.log(state); // 输出 [object Object] { done: true, value: undefined }
这里的规则是:第x调用next()方法时传入的参数,是第x-1次调用yield的返回值。当x=1,也就是第1次调用next()方法时,因为此时还从来没有调用过yield,因此输入参数会被丢弃(如上栗例中没有输出B)。此时,如果要传入参数,则应使用generator函数的输入参数。
function* generator() {
console.log("1");
yield "A";
console.log("2");
yield "B";
console.log("3");
return "C";
};
let iterator = generator();
for (let value of iterator) {
console.log(value);
}
上面代码的输出是:
"1"
"A"
"2"
"B"
"3"
注意到并没有输出C,因为这个遍历只对yield有效。既然已经可以利用yiled获得函数任意执行阶段的返回值,所以建议generator函数中不要再使用return,这样可以统一访问方式。将原先需要return的返回值,放到最后一个yield即可。
可以通过yield* 串联Generator函数。
function* gen1() {
yield "A";
yield* gen2();
};
function* gen2(){
yield "B";
yield* gen3();
}
function* gen3(){
yield "C";
}
let iterator = gen1();
for (let value of iterator) {
console.log(value); // 输出: A B C
}
和Promise一样,Generator函数也可以将异步请求中的层层回调,改写成串行化的方式。将add()函数改写成了异步执行的方式,如下例所示:
const add = (x, y, advancer) => {
setTimeout(() => {
let sum = x+y;
advancer(sum);
}, 200);
};
const curry = (method, ...args) => {
var value = (advancer) => {
args.push(advancer);
return method.apply({}, args);
}
return value;
};
const controller = (generator) => {
const iterator = generator();
const advancer = (response) => {
var state;
state = iterator.next(response);
if (!state.done) {
state.value(advancer);
}
}
advancer();
};
function* generator(){
var value = curry(add, 1, 2);
const sum1 = yield value;
const sum2 = yield curry(add, sum1, 3);
const sum3 = yield curry(add, sum2, 4);
console.log(sum1, sum2, sum3);
}
controller(generator);
上面代码的核心在于,add方法的回调函数并非直接写死,而是改成参数,由advancer递归传入,这样使得只要state.done为false,就会依次串行执行。
这篇文章简要地介绍了Generator函数及其作用,对于想要熟悉Redux/Saga的同学来说,算是一个前导的知识点。
感谢阅读,希望这篇文章能给你带来帮助!