javascript--深度剖析异步解决方案高级语言层出不穷,唯独js脱颖而出。这就是js的设计理念,js天生就是异步Born,正如布道者蒲灵在node中深入浅出的解释--(有兴趣的可以看看,很有意思^_^),异步很早就存在于操作系统的底层,没想到,在大多数高级编程语言中,异步是很少见的,似乎被屏蔽了。出现这种现象的原因可能令人惊讶。程序员不适合通过异步编程^_^。异步的概念很好,但是在程序员的编程过程中确实存在一些问题。并不是说这个概念不能接受,而是当有大量的异步操作时,你的代码的可读性会降低,回调函数异步编程很容易产生破坏陷阱,即回调地狱——(别着急,后面会详细解释)不过,js社区一直没有停下脚步,最新的ES7推出了async/await终极异步解决方案,说终极可能不严禁,但是它有确实彻底解放了原有的模块侵入式异步编程,让程序员可以实现接近于传统函数调用的异步编程,这是js里程碑式变革中极其重要的一环。Javascript异步编程解决方案的历史ES6之前:回调函数回调函数是最原始的异步编程解决方案。上一篇文章中已有介绍,这里不再赘述。这是门户回调函数的美妙之处。但是如果业务逻辑过多,回调函数会产生深嵌。set,对程序员很不友好,如下代码所示,有一个业务逻辑需要一次读取三个文件a,b,cvarfs=require('fs');fs.readFile('./a.txt',function(err1,data1){fs.readFile('./b.txt',function(err2,data2){fs.writeFile('./ab.txt',data1+data2,function(err){console.log('读写完成!');});});});三个异步函数的嵌套看似很简单。这里的知识只是假设。如果有5个、10个甚至更多的异步函数要顺序执行,那就应该嵌套了(大家不喜欢横向增长,哈哈)说实话挺吓人的,代码会变得异常难读,调试、维护。这就是所谓的回调地狱或回调地狱。正是为了解决这个问题,我们才有了接下来两节要讲的内容,使用promise或者generator进行异步进程管理。说白了,异步进程管理就是为了解决回调地狱的问题。所以一切都有两个方面。异步编程有其独特的优势,但同时也遇到了同步编程所没有的代码组织问题。事件监听(事件发布/订阅)事件监听方式是异步编程中广泛使用的一种方式,是回调函数的事件化,即发布/订阅方式,varutil=require('util');varevents=require('事件');functionStream(){events.EventEmitter.call(this);}util.inherits(Stream,events.EventEmitter)让got=newStream();got.on("完成",function(params){console.log(params);});got.on("完成",function(params){console.log('QWER');});got.emit("done",'diyige');控制台.log('----------------');varemitter=newevents.EventEmitter();emitter.on("done",function(params){console.log(params);});emitter.on("done",function(params){console.log('ZXCV');});emitter.emit("完成",'dierge');//DIYge//QWER//dierge//ZXCVPromiseobjectPromise是一种异步编程的解决方案,它比传统的解决方案——回调函数和事件——更合理、更强大,其目的是取代之前的回调函数而不是。编程方案也是后面介绍的异步方案的基础。它是由社区首先提出并实施的。ES6将其写入语言标准,统一使用,原生提供Promise对象。现在几乎所有的js库都支持这种异步方案的promise对象,具有以下特点:对象的状态不受外界影响。Promise对象表示一个异步操作,具有三种状态:pending(进行中)、fulfilled(成功)和rejected(失败)。只有异步操作的结果才能确定当前处于哪个状态,其他任何操作都不能改变这个状态。这也是Promise这个名字的由来,英文是“承诺”的意思,意思是一旦状态发生变化,就无法通过其他方式改变,随时都可以得到这个结果。改变Promise对象的状态只有两种可能性:从pending到fulfilled和从pending到rejected。只要这两种情况发生,状态就会冻结,不会再发生变化,这个结果就会一直保持下去。这时候就叫解决了。如果变化已经发生,如果你给Promise对象添加一个回调函数,你会立即得到结果。这与事件(Event)完全不同。事件的特点是错过了再听,就得不到结果。下面将该方法应用于单个promise对象varpromise=newPromise(function(resolve,reject){//...一些代码if(/*asyncoperationsucceeded*/){resolve(value);}else{reject(错误);}});通常我们在使用promise的时候,一般都会对其对应的业务进行包装。如下图模拟了一个读取文件的异步promise函数,varreadFile=function(params){returnnewPromise(function(resolve,reject){setTimeout(function(){resolve(params);},2000);});}readFile('file1').then(function(data){console.log(data);returnreadFile('file2')}).then(function(data){console.log(data);returnreadFile('file3')}).then(function(data){console.log(data);returnreadFile('file4')}).then(function(data){console.log(data);returnreadFile('file5')}).then(function(data){console.log(data);})//file1//file2//file3//file4//file5流程控制库也需要手动调用来处理后续任务。这里我们只简单介绍一种,我们称之为尾部触发。接下来是常用的关键字。为什么?或者我们要说的是因为是node神级框架express中采用的模式,这里可能会涉及到一些后端node内容。node搭建服务器时,需要面向切面编程,需要各种中间件varapp=connect();//中间件app.use(connect.staticCache());app.use(connect.static(__dirname+'/public'));app.use(connect.cookieParser());应用程序。使用(连接。会话());app.use(connect.query());app.use(connect.bodyParser());app.use(connect.csrf());app.listen(3001);在通过use()方法监听一系列中间件后,监听端口上的请求。中间件使用尾触发机制。下面是一个简单的中间件函数(req,res,next){//expressmiddleware}每个中间件通过request对象,response对象,tail触发函数,通过队列形成一个处理流程,如下图,中间件机制在处理网络请求时实现过滤、验证、日志记录等类似面向切面编程的功能。ES6:Generatorfunction(coroutine)Generator函数有多个理解角度从语法上来说,Generator函数是一个状态机,封装了多个内部状态.执行Generator函数会返回一个遍历器对象,也就是说Generator函数不仅是一个状态机,还是一个遍历器对象生成函数。函数执行后返回一个遍历器对象,可以依次遍历Generator函数内部的各个状态。函数*helloWorldGenerator(){yield'hello';产生“世界”;返回“结束”;}varhw=helloWorldGenerator();hw.next()//{value:'hello',done:false}hw.next()//{value:'world',done:false}hw.next()//{value:'ending',done:true}hw.next()//{value:undefined,done:true}Next在一个步骤中,必须调用遍历器对象的next方法将指针移动到下一个状态。也就是说,每次调用next方法时,内部指针都从函数头部或上次停止的地方开始执行,直到遇到下一个yield表达式(或return语句)。也就是说,Generator函数是分段执行的,yield表达式是暂停执行的标记,next方法可以恢复执行。基于Promise对象的generator/yield函数的自动执行并不能真正解决异步方案的问题,需要配合额外的执行模块比如TJHolowaychuk的co模块。这里使用promise模块自动执行生成器函数;varfs=require('fs');varreadFile=function(fileName){returnnewPromise(function(resolve,reject){fs.readFile(fileName,function(error,data){if(error)returnreject(error);resolve(data);});});};vargen=function*(){varf1=yieldreadFile('/etc/fstab');varf2=yieldreadFile('/etc/shells');console.log(f1.toString());console.log(f2.toString());};/*********************************************varg=gen();g.next().value.then(函数(数据){g.next(数据).value.then(函数(数据){g.next(数据);});});********************************************///自动执行函数functionrun(gen){v??arg=创();functionnext(data){varresult=g.next(data);如果(结果。完成)返回结果。值;结果.value.then(函数(数据){next(数据);});}下一个();}运行(生成);ES7:async/await终于到了我们梦寐以求的“终极”异步解决方案,也许你有点失望。当然,这个失望是async/await只是语法糖,async/await是generator/yield/promise+自动执行模块的封装。相比于前身async函数可以自动执行,await关键字后面只能跟一个promise编队——这里注意await支持其他数据类型,但底层也会将其转为promise对象。async函数对Generator函数的改进体现在下面四个内置的Actuator。generatorfunction的执行必须依赖executor,所以有了co模块,asyncfunction有自己的executor,和generatorfunction完全不同。需要调用next方法或者使用co模块来真正执行,得到最终的结果。更好的语义。async和await的语义比星号和yield更清晰。async表示函数中有异步操作,await表示紧跟在后面的表达式需要等待结果。适用性更广。co模块约定yield命令后只能跟Thunk函数或Promise对象,而async函数的await命令后可以跟Promise对象和原始类型值(数字、字符串和布尔值,但这相当于一个同步操作)返回值是一个Promise。async函数的返回值是一个Promise对象,这比Generator函数的返回值是一个Iterator对象要方便的多。您可以使用then方法指定下一步操作。更进一步,async函数可以看做是多个异步操作封装到一个Promise对象中,await命令是内部then命令的语法糖。函数名称(参数){返回新的承诺(函数(解决,拒绝){setTimeout(()=>{解决(参数)},3000);});}asyncfunctionmyf(){letgf=awaitname('小花');letgf2=awaitname('小红');returngf+gf2}asyncfunctionmyf3(params){让aaa=awaitmyf();返回aaa;}myf3().then(function(params){console.log(params);});//xiaohuaxiaohongasync/await高度封装了前者的generator/yield,那些支持promise实现的库可以像普通函数一样完美调用,async函数和其他async函数也可以完美无缝对接。堪称终极解决方案。koa2已经支持async/await,但是最新的express框架还是不支持这种写法。async/await是大势所趋,也许express会在不久的将来支持,我们拭目以待
