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>
时间: 2024-07-30 10:18:09

ES6-Async & 异步的相关文章

ES6/7 异步编程学习笔记

前言 在ES6的异步函数出现之前,Js实现异步编程只有settimeout.事件监听.回调函数等几种方法 settTmeout 这种方法常用于定时器与动画的功能,因为其本质上其实是浏览器的WebAPIs功能,因此与主线程并不同,当延时方法到达触发条件时,方法被添加到用于回调的任务队列,只要执行引擎栈中的代码执行完毕,主线程就会去读取任务队列,依次执行那些满足触发条件的回调函数.所以其等待时间在某些情况下往往不是那么准备,这一方面推荐可以看看<你不知道的js>这方面的章节. 事件监听 这一个方法

ES6 async await

1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>async-await</title> 6 </head> 7 <body> 8 <h3>ES6 async 函数用法</h3> 9 10 <script> 11 12 wind

使用spring的@Async异步执行方法

应用场景: 1.某些耗时较长的而用户不需要等待该方法的处理结果 2.某些耗时较长的方法,后面的程序不需要用到这个方法的处理结果时 在spring的配置文件中加入对异步执行的支持 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.spri

Async异步编程入门示例

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace AsyncDemo 8 { 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 Console.WriteLine("异步方法测试开始!&quo

js:nodejs通过async异步提交事务数据

一,async异步编程 1,异步编程是指由于异步I/O等因素,无法同步获得执行结果时, 在回调函数中进行下一步操作的代码编写风格,常见的如setTimeout函数.ajax请求等等 waterfall函数 瀑布流 waterfall每个函数产生的值,都将传给下一个函数 2,事务 数据库事物(transaction)就是一组SQL语句,这组SQL语句是一个逻辑工作单元.我们可以认为事务就是一组不可 分割的SQL语句,在执行时,应该作为一个整体永久性地修改数据库的内容,或者作为一个整体取消对数据库的

ES6 async/await语法

Async/await的主要益处是可以避免回调地狱(callback hell)问题 Chromium JavaScript引擎 从v5.5开始支持async/await功能,Chromium JavaScript引擎版本号的查看方法是:在浏览器地址栏输入 chrome://version,查询出来的信息如下图所示: 这意味着async/await不再是实验性功能,使用它时无需指定--harmony参数,此参数用来开启那些几乎已完成但被V8团队认为还不稳定的功能.不同的node版本使用不同的V8

第二章 有什么理由使用Async异步编程

p { display: block; margin: 3px 0 0 0; } --> 写在前面 在学异步,有位园友推荐了<async in C#5.0>,没找到中文版,恰巧也想提高下英文,用我拙劣的英文翻译一些重要的部分,纯属娱乐,简单分享,保持学习,谨记谦虚. 如果你觉得这件事儿没意义翻译的又差,尽情的踩吧.如果你觉得值得鼓励,感谢留下你的赞,在今后每一次应该猛烈突破的时候,不选择知难而退.在每一次应该独立思考的时候,不选择随波逐流,应该全力以赴的时候,不选择尽力而为,愿爱技术的园

利用Spring MVC中@Async异步特性改善耗时操作的用户体验

Web应用中,有时会遇到一些耗时很长的操作(比如:在后台生成100张报表再呈现,或 从ftp下载若干文件,综合处理后再返回给页面下载),用户在网页上点完按钮后,通常会遇到二个问题:页面超时.看不到处理进度. 对于超时,采用异步操作,可以很好的解决这个问题,后台服务收到请求后,执行异步方法不会阻塞线程,因此就不存在超时问题.但是异步处理的进度用户也需要知道,否则不知道后台服务何时完成,不知道是否继续等候,还是关掉页面. 思路: 1.browser -> Spring-MVC Controller

springboot+async异步接口实现和调用

什么是异步调用? 异步调用是相对于同步调用而言的,同步调用是指程序按预定顺序一步步执行,每一步必须等到上一步执行完后才能执行,异步调用则无需等待上一步程序执行完即可执行. 如何实现异步调用? 多线程,这是很多人第一眼想到的关键词,没错,多线程就是一种实现异步调用的方式. 在非spring目项目中我们要实现异步调用的就是使用多线程方式,可以自己实现Runable接口或者集成Thread类,或者使用jdk1.5以上提供了的Executors线程池. StrngBoot中则提供了很方便的方式执行异步调

async异步注解和aspect切面注解等注解的原理

在我们使用spring框架的过程中,在很多时候我们会使用@async注解来异步执行某一些方法,提高系统的执行效率.今天我们来探讨下spring是如何完成这个功能的. 1.spring 在扫描bean的时候会扫描方法上是否包含@async的注解,如果包含的,spring会为这个bean动态的生成一个子类,我们称之为代理类(?). 2.代理类是继承我们所写的bean的,然后把代理类注入进来,在执行此方法时会到代理类中,代理类判断此方法需要异步执行,就不会调用父类 (我们原本写的bean)的对应方法.