前言
在《ES6 异步编程之一:Generator》中实现了一个异步函数调用链,它是一个顺序调用链,很类似责任链模式,但现实往往不是平铺直叙的,更多的其实是峰回路转,本文将继续讨论更多Generator
的用法。
作为函数的Generator
在之前的示例中,我们更多的是把生成器作为一个迭代器来循环调用每个函数,但忽视了生成器也是个函数,意味着生成器内部可以实现复杂的业务逻辑,以下的代码通过yield
等待文件读取结果并对结果进行特定的处理,
function* generator() {
let r1 = yield get('a');
if (r1) {
let r2 = yield get('b');
if (r2) {
console.log(yield get('d'));
}
} else {
console.log(yield get('c'));
}
}
let g = generator();
g.next();
如果get
是个异步调用,以上的代码想要能够执行则需要get
函数执行得到结果后调用生成器的next
方法来推进,这要求get
函数能持有生成器对象,这显然并不容易。
偏函数
偏函数(Partial Function)是对函数定义域的子集定义的函数,形式上就是指定任意部分参数生成一个新的函数,如下:
function sum(a, b, c) {
return a + b + c;
}
function sum1(a) {
return function(b, c) {
return a + b + c;
};
}
function sum2(a, b) {
return function(c) {
return a + b + c;
};
}
sum(1, 2, 3) == sum1(1)(2, 3); //true
sum(1, 2, 3) == sum2(1, 2)(3); //true
一般在设计异步调用api时,我们总是声明一个参数来接收回调函数,当和偏函数相结合就变成这样:
function get(f, callback) {
delay(100, function(s) {
callback(s + ':get ' + f);
});
}
get('a', func); //调用get时必须立即传入一个函数
//转换成偏函数形式:
function partialGet(f) {
return function(callback) {
delay(100, function(s) {
callback(s + ':get ' + f);
});
};
}
let pGet = partialGet('a'); //可以先生成一个函数
pGet(func); //需要时再传入回调函数执行
从上面的例子中可以发现,偏函数能使定义和执行分离,说来巧了,生成器可用于定义业务逻辑而生成器的next
用于推进业务执行,二者也是相互分离的。
生成器和偏函数
基于前面这么多铺垫,假设get
就是一个偏函数,如下:
function get(f) {
return function(callback) {
delay(100, function(s) {
callback(s + ':get ' + f);
});
};
}
这意味着,yield get('a')
使得next
函数执行的结果其value
属性值是个函数,该函数的参数是一个能接收get
异步结果的回调函数,即:
g.next().value(function(value) {
g.next(value); //value成为yield的返回并继续推进业务逻辑
});
通过递归可以不断的执行生成器的next
方法,一个全新的通过生成器来实现业务逻辑的run
方法便呼之欲出了,
function run(gen) {
let g = gen();
function next(lastValue) {
let result = g.next(lastValue); //将上一个异步执行结果传出给当前的yield并执行下一个yield
if (result.done) {
return result.value;
}
//value是偏函数返回的新函数,它的参数是个用来接收异步结果的回调函数
result.value(next); //next作为接收异步结果的回调函数
}
next();
}
run(generator);
综合以上例子不难发现另外一个好处,通过偏函数可以使异步调用api不受生成器的侵入,《ES6 异步编程之一:Generator》中实现的异步调用需要将生成器作为参数,有兴趣的话你可以尝试改造一下之前的示例。
现在通过新编写的run
函数就可以来执行本文一开始编写的那个生成器了。
thunkify
偏函数的方案对api和生成器也是有侵入的,他要求:
api必须是偏函数形式;
生成器定义业务逻辑,每个yield 后面的函数必须是调用偏函数;
第二个问题是本方案的核心机制所要求,但第一个问题我们可以通过thunkify
来解决,api依旧按照get(value, callback)
方式定义,但定义生成器时需要将异步api调用通过thunkify
转换为偏函数,如下:
let Thunkify = require('thunkify');
let thunkifiedGet = Thunkify(get);
function get(f, callback) {
delay(100, function(s) {
callback(s + ':get ' + f);
});
}
function* generator() {
let r1 = yield thunkifiedGet('a');
if (r1) {
let r2 = yield thunkifiedGet('b');
if (r2) {
console.log(yield thunkifiedGet('d'));
}
} else {
console.log(yield thunkifiedGet('c'));
}
}