• ES6-Async & 异步


    依赖文件地址 :https://github.com/chanceLe/ES6-Basic-Syntax/tree/master/js

      1 <!DOCTYPE html>
      2 <html>
      3     <head>
      4         <meta charset="UTF-8">
      5         <title>[es6]-16-异步操作和Async函数</title>
      6         <script src="./js/browser.js"></script>
      7         <script src="./js/babel-pollyfill.js"></script>
      8         <script type="text/babel">
      9             /*
     10              * 异步编程对js语言太重要。js语言的执行环境是单线程的,如果没有异步编程,根本没法用。
     11              * 
     12              * ES6以前,异步编程的方法,大概有四种:
     13              *   回调函数  事件监听   发布/订阅  Promise对象
     14              * 
     15              * ES6将js异步编程带入了一个新阶段,ES7的Async函数更是提出了异步编程的终极解决方案。
     16              * 
     17              * 所谓异步,就是一个任务分成两段,先执行一段,然后转而执行其他任务,等做好了准备,再回头执行第二段。
     18              * 这种不连续的执行,就叫异步。
     19              * 相应的,连续的执行叫同步,由于是连续执行,所以要等待IO,这时候程序暂停,cpu浪费。
     20              * 
     21              * 回调函数:
     22              * js语言对异步编程的实现,就是回调函数。所谓回调函数,就是把任务的第二段单独写在一个函数里面。
     23              * 等到重新执行这个任务的时候,就直接调用这个函数。
     24              * 回调函数不多说,这里只说Node.js约定,回调函数的第一个参数是err,原因是执行分成两段,这两段之间抛出的
     25              * 错误,程序无法捕捉,只能当做参数,传入第二段。
     26              * 
     27              * Promise:
     28              * 回调函数本身并没有问题,问题出在多个回调函数嵌套。如果多重嵌套,代码就横向发展,很快就乱成一团,这种
     29              * 情况称为 回调函数噩梦。
     30              * Promise就是为了解决回调函数噩梦的问题提出的。它不是新的语法功能,而是一种新的写法,允许将回调函数的嵌套,
     31              * 改成链式调用。
     32              * 
     33              * Promise 的写法只是回调函数的改进,使用then方法后,异步任务的两段执行看的更清楚了,除此之外,并无新意。
     34              * Promise的最大问题是代码冗余,原来的任务被Promise包装了以下,不管什么操作,一眼看去都是then,原来的语义
     35              * 变得很不清楚。
     36              * 
     37              * 更好的写法...
     38              * Generator函数
     39              * 协程:传统的编程语言,早有异步编程解决方案。其中有一种叫做协程,意思就是多个线程互相协作,完成异步任务。
     40              *    协程有点像函数,又有点像线程。运行流程大致如下:
     41              *     第一步,协程A开始执行。
     42              *            第二步,协程A执行到一半,进入暂停,执行权转到协程B。
     43              *     第三步,(一段时间后)协程B交还执行权。
     44              *     第四步,协程A恢复执行。
     45              * 举例来说,读取文件的协程写法如下:
     46              *   function* asyncJob(){
     47              *       // 其他代码
     48              *    var f = yield readFile(fileA);
     49              *    //其他代码
     50              * }
     51              * 上面代码的奥妙在于其中的yield命令。它表示执行到此处,执行权交给其他协程。也就是说yield是异步两个阶段的分界线。
     52              * 协程遇到yield命令会暂停,等到执行权返回,再从暂停的地方继续往后执行。它最大的优点是代码的写法
     53              * 非常像同步操作,除了yield命令,几乎一模一样。
     54              * 
     55              * Generator函数的概念
     56              *  Generator函数是协程在ES6的实现,最大的特点就是可以交出函数的执行权(即暂停执行)。
     57              *  整个Generator函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用yield语句注明。
     58              *  
     59              * 
     60              * 异步任务的封装:
     61              * var fetch = require("node-fetch");
     62              * function *gen(){
     63              *     var url = 'https://api.github.com/users/github';
     64              *  var result = yield fetch(url);
     65              * console.log(result.bio)
     66              * }
     67              * 
     68              * 上面代码中,Generator函数封装了一个异步操作,该操作先读取一个远程接口,然后从json格式的数据解析信息。
     69              * 执行这段代码的方法如下:
     70              * var g = gen();
     71              * var result = g.next();
     72              * result.value.then(function(data){
     73              *     return data.json();
     74              * }).then(function(data){
     75              *     g.next(data);
     76              * })
     77              * 
     78              * 虽然Generator函数将异步操作表示的很简洁,但是流程管理却不方便,(什么时候执行第一段,什么时候执行第二段)
     79              */
     80             
     81             /*
     82              * Thunk函数
     83              * 参数的求值策略:
     84              * Thunk函数在上世纪60年代就诞生了。那时,编程语言刚起步,计算机科学家还在研究,编译器怎么写比较好。
     85              * 争论的一个焦点是  求值策略,即函数的参数到底应该何时求值。
     86              * 
     87              * 一种意见是传值调用,即在进入函数体之前,就计算参数表达式的值。
     88              * 另一种是传名调用,直接将函数的参数表达式传入函数体,只在用到它的时候求值,Haskell语言采用这种策略。
     89              * 
     90              * 传值调用比较简单,时但是催参数求值的时候,实际上还没用到这个参数,有可能造成性能损失。
     91              * 
     92              * Thunk函数的含义:
     93              * 编译器的传名调用的实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体,这个临时函数就叫做Thunk函数。
     94              */
     95             {
     96                 var x = 2;
     97                 function f(m){
     98                     return m*2;
     99                 }    
    100                 console.log(f(x+5));  //14  
    101                 //等同于
    102                 var thunk = function(){
    103                     return x+5;
    104                 }
    105                 function f1(thunk){
    106                     return thunk()*2;
    107                 }
    108                 
    109                 //上面代码中,函数f的参数x+5被一个函数替换了。凡是用到原参数的地方,对thunk函数求值即可。
    110             }
    111             //这就是thunk函数的定义:它是传名调用的一种实现策略,用来替换某个表达式。
    112             
    113             /*
    114              * js中的Thunk函数
    115              * js是传值调用,它的Thunk函数含义有所不同。在js语言中,Thunk函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本
    116              * ,且只接受回调函数作为参数。
    117              */
    118             /*
    119             {
    120                 //正常版本的readFile(多参数版本)
    121                 fs.readFile(filename,callback);
    122                 //Thunk版本的readFile(单参数版本)
    123                 var readFileThunk = Thunk(fileName);
    124                 readFileThunk(callback);
    125                 
    126                 var Thunk = function(fileName){
    127                     return function(callback){
    128                         return fs.readFile(fileName,callback);
    129                     }
    130                 }
    131                 
    132                 //上面代码中,fs模块的readFile方法是一个多参数函数,两个参数分别是文件名和回调函数。
    133                 //经过转换器处理,它变成了一个单参数函数,只接受回调函数作为参数。
    134                 //这个单参数版本,就叫做Thunk函数。
    135             }
    136             //任何函数,只要参数有回调函数,就能协程Thunk函数的形式。下面是一个简单的Thunk函数转换器
    137         
    138             {
    139                 //es5版本
    140                 var Thunk = function(fn){
    141                     return function(){
    142                         var args = Array.prototype.slice.call(arguments);
    143                         return function(callback){
    144                             args.push(callback);
    145                             return fn.apply(this,args);
    146                         }
    147                         
    148                     }
    149                 }
    150             }
    151             {
    152                 //es6 版本
    153                 var Thunk = function(fn){
    154                     return function(...args){
    155                         return function(callback){
    156                             return fn.call(this,...args,callback);
    157                         }
    158                     }
    159                 }
    160             }
    161             */
    162             /*
    163              * Thunk函数在以前确实没什么用,但有了ES6的Generator函数之后,Thunk函数可以用于Generator函数的
    164              * 自动流程管理。
    165              */
    166             //Generator函数可以自动执行
    167             
    168              {
    169                  function* gen(){
    170                      //some code
    171                      yield 1;
    172                      yield 2;
    173                      yield 3;
    174                      yield 4;
    175                  }
    176                  var g = gen();
    177                  var res = g.next();
    178                  
    179                  while(!res.done){
    180                      console.log(res.value);
    181                      res = g.next();
    182                  }
    183              }
    184              /*
    185              上面代码中,Generator函数gen会自动执行完所有步骤。但是,这并不适合异步操作。
    186              如果必须保证前一步执行完,后一步才执行,那上面的自动执行就不可行。这时Thunk函数就能派上用场。
    187              在Generator函数中,yield命令用于将程序的执行权移出Generator函数,那么需要一种方法将
    188              执行权还给Generator函数。这种方法就是Thunk函数,因为它可以在回调函数里,将执行权交还给
    189              Generator函数。
    190              */
    191             
    192             //Thunk函数真正的威力,在于可以自动执行Generator函数。
    193             //下面是一个基于Thunk函数的Generator执行器。
    194             
    195             function run(fn){
    196                 var ge = fn();
    197                 function next(err,data){
    198                     var result = ge.next(data);
    199                     if(result.done) return;
    200 //                    result.value(next);
    201                     console.log(result.value);
    202                     result.value = next();
    203                     
    204                 }
    205                 next();
    206             }
    207             function* g4(){
    208                 yield 1;
    209                 yield 2;
    210                 yield 3;
    211                 yield 4;
    212             }
    213             run(g4);
    214             /*
    215              * 上面的代码的run函数,就是一个Generator函数的自动执行器。内部的next函数就是Thunk的
    216              * 回调函数。next先将指针移到Generator函数的下一步。然后判断Generator函数是否结束,如果没结束,就将
    217              * next函数,再传入Thunk函数(result.value属性),否则直接退出。
    218              * 有了这个执行器,执行Generator函数方便多了。不管内部有多少个异步操作,直接把Generator函数传入
    219              * run函数即可。当然前提是,每个异步操作都要是Thunk函数,也就是说跟在yield命令后面的必须是Thunk函数。
    220              * 
    221              * var g = function*(){
    222              *     var f1 = yield readFile("fileA")
    223              * var f2 = yield readFile("fileB")
    224              * var f3 = yield readFile("fileC")
    225              * ...
    226              * var f4 = yield readFile("fileD")
    227              * }
    228              * run(g);
    229              * 
    230              * 上面代码中,函数g封装了n个异步的读取文件操作,只要执行run函数,这些操作就会自动完成。
    231              * 这样一来,异步操作不仅可以写的像同步操作,而且一行代码就可以执行。
    232              * 
    233              * Thunk函数并不是唯一自动执行Generator函数的方案。因为自动执行的关键是,必须有一种机制,
    234              * 自动控制Generator函数的流程,接收和交还程序的执行权。回调函数可以做到这一点,Promise对象也可以做到这一点。
    235              */
    236             
    237             /*
    238              * co模块
    239              * 是著名程序员TJ Holowaychuk于2013年6月发布的小工具,用于Generator函数的自动执行。
    240              * 比如有一个Generator函数,co模块可以让你不用编写Generator函数的执行器。
    241              * var co = require('co');
    242                 co(gen);
    243                 上面代码中,Generator函数只要传入co函数,就会自动执行。
    244                 co函数返回一个Promise对象,因此可以用then方法添加回调函数。
    245                 
    246                 co(gen).then(function(){
    247                     console.log("Generator函数执行完成。")
    248                 })
    249                 
    250               co模块的原理?
    251               Generator就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。
    252               两种方法可以做到这点:
    253               1.回调函数。 将异步操作包装成Thunk函数,在回调函数里面交回执行权。
    254               2.Promise对象。将异步对象包装成Promise对象,用then方法交回执行权。
    255               
    256               co模块其实就是将两种自动执行器(Thunk函数和Promise对象),包装成一个模块。
    257               使用co的前提条件是,Generator函数的yield后面,只能在是Thunk函数或Promise对象。
    258              */
    259             
    260             /*
    261              *基于Promise对象的自动执行。
    262              * 跟Thunk函数的差别就是用then方法,层层添加回调函数具体实现如下
    263              */
    264             /*
    265              function run(gen){
    266                  var g = gen();
    267                  function next(data){
    268                      var result = g.next(data);
    269                      if(result.done){
    270                          return result.value;
    271                      }
    272                      result.value.then(function(data){
    273                          next(data);
    274                      })
    275                  }
    276                  next();
    277              }
    278              
    279              run(gen);
    280              //上面代码中,只要Generator函数还没执行到最后一步,next函数就调用自身,以此实现自动执行。
    281              * */
    282             
    283             
    284             /*
    285              * co模块是Generator自动执行器的扩展,源码只有几十行非常简单。
    286              * 首先,co函数接受Generator函数作为参数,返回一个Promise对象。
    287             
    288             
    289             function co(gen){
    290                 var ctx = this;
    291                 return new Promise(function(resolve,reject){
    292                 })
    293             }
    294              */
    295             /*
    296              * 在返回的Promise对象里面,co先检查函数gen是否为Generator函数。如果是,就执行该函数,得到一个内部指针对象
    297              * 如果不是就返回,并将Promise的状态改为resolved。
    298              
    299             function co(gen){
    300                 var ctx = this;
    301                 return new Promise(function(resolve,reject){
    302                     if(typeof gen === 'function'){
    303                         gen = gen.call(ctx);
    304                     }
    305                     if(!gen || typeof gen.next !== 'function'){
    306                         return resolve(gen);
    307                     }
    308                 })
    309             }
    310             */
    311             
    312             /*
    313              * 接着,co将Generator函数的内部指针对象的next方法,包装成onFullfilled函数。
    314              * 这主要是为了能够捕捉抛出的错误。
    315              */
    316             
    317             function co(gen){
    318                 var ctx = this;
    319                 return new Promise(function(resolve,reject){
    320                     if(typeof gen === 'function'){
    321                         gen = gen.call(ctx);
    322                     }
    323                     if(!gen || typeof gen.next !== 'function'){
    324                         return resolve(gen);
    325                     }
    326                     
    327                     onFullfilled();
    328                     function onFullfilled(res){
    329                         var ret;
    330                         try {
    331                             ret = gen.next(res);
    332                         }catch(e){
    333                             return reject(e);
    334                         }
    335                         next(ret);
    336                     }
    337                 })
    338             }
    339             //最后,就是关键的next函数,它会反复调用自身。
    340             function next(ret){
    341                 if(ret.done){
    342                     return resolve(ret.value);
    343                 }
    344                 var value = toPromise.call(ctx,ret.value);
    345                 if(value && isPromise(value)){
    346                     return value.then(onFullfilled,onRejected);
    347                 }
    348                 return onRejected(new TypeError("You may only yield a function,promise,generator,"
    349                 +"array or object,but the following object was passed:'"+String(ret.value)+"'"));
    350             }
    351             
    352             /*
    353              * 上面代码中,next函数的内部代码,
    354              * 第一行,检查当前行为是否为Generator函数的最后一步,如果是就返回。
    355              * 第二行确保每一步的返回值,是Promise对象。
    356              * 第三行,使用then方法,为返回值加上回调函数,然后通过onFullfilled函数再次调用next函数。
    357              */
    358             
    359             /*
    360              * 处理并发的异步操作
    361              * co支持并发的异步操作,即允许某些操作同时进行,等到他们全部完成,才进行下一步。
    362              * 这时,要把并发的操作都放在数组或对象里面,跟在yield语句后面。
    363              *
    364              */
    365             //数组的写法
    366             co(function*(){
    367                 var res = yield [
    368                 Promise.resolve(1),
    369                 Promise.resolve(2)
    370                 ];
    371                 console.log(res);
    372             }).catch(onerror);
    373             //对象的写法
    374             co(function*(){
    375                 var res = yield {
    376                     1:Promise.resolve(1),
    377                     2:Promise.resolve(2)
    378                 };
    379                 console.log(res);
    380             }).catch(onerror);
    381             
    382             //下面还有一个例子
    383             co(function*(){
    384                 var values = [n1,n2,n3];
    385                 yield values.map(somethingAsync);
    386             });
    387             function* somethingAsync(x){
    388                 //do something async
    389                 return y;
    390             }
    391             //上面的代码允许并发三个somethingAsync异步操作,等到他们全部完成,才会进行下一步。
    392         </script>
    393     </head>
    394     <body>
    395     </body>
    396 </html>
  • 相关阅读:
    mysql数据库查询库中所有表所占空间大小
    mysql行转列
    mysql重置密码
    POJ1426 Find The Multiple —— BFS
    POJ3279 Fliptile —— 状态压缩 + 模拟
    POJ1077 Eight —— IDA*算法
    POJ1077 Eight —— A*算法
    POJ1077 Eight —— 双向BFS
    POJ1077 Eight —— 反向BFS
    POJ1077 Eight —— 正向BFS
  • 原文地址:https://www.cnblogs.com/chengyunshen/p/7191700.html
Copyright © 2020-2023  润新知