ES6之Generator函数异步运用

245 阅读2分钟

目前我们已经了解到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还有更多的运用,这需要靠大家集体智慧去挖掘。