目前我们已经了解到Generator函数迭代的运用,上一篇中迭代的运用给我们了一个很大的想象空间,今天我们继续讨论Generator函数,在上一章中,有提到过Generator函数可以看作迭代器,也可以当做状态机,迭代我们已经了解,但状态机与异步如何使用呢?本章节今天将重点讨论异步的运用。
在讨论异步运用之前,我们先来看看状态机,我们平时项目开发时,很多人都有使用过jquery ajax 的done函数,我们知道ajax 默认是一种异步的方法,但是能够在$.ajax调用之后可以使用done是怎么回事呢,那就是因为jquery对异步的函数使用了状态机,如下:
var state = 0;
function done(status){
if(state === 2)
return;
state = 2;
var isSuccess = status >= 200 && status < 300 || status === 304;
if (isSuccess) {
deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
} else {
deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
}
jqXHR.statusCode( statusCode );
}
这是jquery在ajax里面使用状态机的大概实现,我们写状态机时,一般是根据自己的功能业务来编写,大致是一个原理,比如:
var state = false;
function mark(){
console.log("current state : ", state);
state = !state;
}
都是用一个字段或者属性来记录一个状态,然后通用的调用done或者mark来改变状态,我们现在来看看Generator中的状态机,如下:
function* mark(s){
while(true){
yield s = !s;
}
}
与第一个mark相比,Generator的状态机更简洁,更容易被理解,安全性更高,有了状态机之后,我们在来讨论异步函数,我们知道JavaScript是一种单线程的脚本语言,只能够逐行持行代码,不能同时运行两个块的代码,但是JavaScript给出异步接口,比如ajax ,setTimeout, setInterval,setTimeout与setInterval虽然可以异步持行,但是也是按列队的排列的方式在指定的时间暂停所有代码持行,将持行权交给列队中的函数,所以也只能算是半异步的工作,利用Generator函数来做异步运用也是同样的道理,只是半异步,不能做到真正的异步持行,但是可以达到我们项目中预期效果。
在真实项目中,我相信大家肯定有遇到过ajax嵌套ajax的经验,这给代码阅读困难,维护也不方便,体验更加不好,我们现在来实现一下Generator函数异步实现:
//模拟ajax
function ajax(url, successFn, errorFn){
this.url = url;
this.successFn = successFn;
this.errorFn = errorFn;
}
ajax.prototype.run = function(){
let me = this;
console.log(`request ${this.url}`);
if(true){
setTimeout(function(){
me.successFn({ msg :"ok", result : { id :1, name :"Song"} })
},1000);
}else{
setTimeout(function(){
me.errorFn({ msg :"no", result :null})
},1000);
}
}
//状态机 用于自动提交异步的持行权 fasle === 当前函数持行, true === 提交持行权
function* mark(s){
while(true){
yield (s=!s);
}
}
//异步函数加载器,用Generator来完成异步函数的列队
function* done( mark, ...fn){
//获取当前持行权
let result = mark.next();
//锁定当前对象的扩展,禁止外部私自修改持行列队中的属性
Object.freeze(fn);
let n = 0;
while(true) {
n += 1;
//判断状态机是否提交持行权?
if(result.value && n >= 0) {
//开始新的持行权时,应该要锁上当前状态机
mark.next();
yield (fn[n] && fn[n].run());
//持行完成后,提交新的持行权
mark.next();
}
}
}
done.prototype.resolve=function(data){
//成功持行完成后,应该把当前的持行权给提交
this.next();
}
done.prototype.reject=function(data){
//失败持行后,中断所有的持行
this.mark.next();
}
//app 入口函数
function main() {
//状态机实例对象,默认是不提交持行权
let authority = mark(false);
let ajax1 = new ajax("a.html",function(data) {
console.log("这是第一个ajax!",data);
/*...此处完成异步成功的业务功能内容*/
//拿到异步加载器的成功钩子函数后,响应then
doneIterator.resolve(data)
},function(error){});
let ajax2 = new ajax("b.html",function(data) {
console.log("这是第二个ajax!",data);
/*...此处完成异步成功的业务功能内容*/
//拿到异步加载器的成功钩子函数后,响应then
doneIterator.resolve(data)
},function(error){});
let ajax3 = new ajax("c.html",function(data) {
console.log("这是第三个ajax!",data);
/*...此处完成异步成功的业务功能内容*/
//拿到异步加载器的成功钩子函数后,响应then
doneIterator.resolve(data)
},function(error){});
let ajax4 = new ajax("d.html",function(data){
console.log("这是第四个ajax!",data);
/*...此处完成异步成功的业务功能内容*/
//拿到异步加载器的成功钩子函数后,响应then
doneIterator.resolve(data)
},function(error){});
let doneIterator = done(authority,ajax1,ajax2,ajax3,ajax4);
doneIterator.mark = authority;
doneIterator.next();
}
//run
main();
这个例子运用了Promise概念在里面,不管怎么样,Generator函数还是很强大的,主要还是看我们怎么去看待它,上面的例子中,不仅可以运用在ajax嵌套上,还可以运用在浏览器滚动事件上,也可以运用在某个业务要分两块来运行,比如支付和等待支付这种功能业务,Generator还有更多的运用,这需要靠大家集体智慧去挖掘。