要知其所以然,首先要明白三个概念:1.什么是同步?所谓同步,就是当发出“调用”时,“调用”直到得到结果后才会返回。但是一旦调用返回,你就会得到返回值。也就是说,主动等待这次“调用”结果的是“调用者”。执行此块之后的代码,直到此调用完成。2、什么是异步?“调用”发出后,调用直接返回,所以没有返回结果。换句话说,当发出异步过程调用时,调用者不会立即得到结果。而是在发出“呼叫”后,“被呼叫者”通过状态、通知通知呼叫者,或者通过回调函数处理呼叫。异步调用发出后,不影响后面代码的执行。3.为什么JavaScript需要异步?首先,我们知道JavaScript是单线程的(即使加入了新的webworker,JS本质上还是单线程的)。同步代码是什么意思?这意味着它可能会被阻止。当我们有一个需要很长时间的任务时,如果我们使用同步的方式,那么后面的代码执行就会被阻塞。但是异步不会,我们不会等待异步代码,继续执行异步任务之后的代码。更多优质文章可以戳:https://github.com/YvetteLau/...理解了概念之后,我们就要进入今天的正题了。首先我们想一想:日常工作中主要使用哪些异步方案,这些异步方案的优缺点是什么?最早的异步方案就是回调函数,比如事件的回调,setInterval/setTimeout中的回调。但是回调函数有一个很普遍的问题,就是回调地狱的问题(后面会举例);为了解决回调地狱的问题,社区提出了Promise解决方案,ES6将其写进了语言标准。Promise在一定程度上解决了回调地狱的问题,但是Promise也存在一些问题。比如trycatch无法捕获错误,使用Promise链式调用并没有从根本上解决回调地狱的问题,只是改变而已。措辞。生成器函数是在ES6中引入的。生成器是一种异步编程解决方案。Generator函数是ES6中协程的实现。最大的特点是可以交接功能的执行权。Generator函数可以看作是异步任务的容器,需要挂起。地方用yield语句标记。但是Generator使用起来比较复杂。ES7提出了一种新的异步解决方案:async/await,async是Generator函数的语法糖,async/await让异步代码看起来像同步代码,异步编程开发的目标就是让异步逻辑代码看起来像同步。回调函数--->Promise--->Generator--->async/await.1。回调函数:callback//节点读取文件fs.readFile(xxx,'utf-8',function(err,data){//code});回调函数使用场景(包括但不限于):事件回调回调函数NodeAPI中ajax请求回调函数setTimeout/setInterval回调函数优点:简单。回调函数的缺点:异步回调嵌套会使代码难以维护,不方便统一处理错误,不能使用trycatch和callbackhell(比如先读取A的文本内容,再读取B根据A的文字内容,再根据B的内容读C的内容读B的内容……)。fs.readFile(A,'utf-8',function(err,data){fs.readFile(B,'utf-8',function(err,data){fs.readFile(C,'utf-8',function(err,data){fs.readFile(D,'utf-8',function(err,data){//....});});});});2.PromisePromise在某种程度上为了解决回调地狱问题,Promise最早被社区提出并实现。ES6将其写入语言标准,统一使用,原生提供Promise对象。那我们看看Promise是如何解决回调地狱问题的,还是以上面的readFile为例(先读取A的文本内容,然后根据A的文本内容读取B,再根据B的内容读取C).functionread(url){returnnewPromise((resolve,reject)=>{fs.readFile(url,'utf8',(err,data)=>{if(err)reject(err);resolve(data);});});}read(A).then(data=>{returnread(B);}).then(data=>{returnread(C);}).then(data=>{returnread(D);}).catch(原因=>{console.log(原因);});Promise的优点:状态一旦改变,就不会再改变,随时可以得到结果。异步操作可以通过同步操作的过程来表达,避免了回调函数的层层嵌套。缺点:无法取消Promise。当它处于待处理状态时,无法知道它已经进展到哪个阶段。trycatch无法捕捉到错误。假设有这样一个需求:读取A、B、C三个文件的内容读取成功后,输出最终结果。在Promise之前,我们一般可以使用发布订阅模型来实现:letpubsub={arry:[],emit(){this.arry.forEach(fn=>fn());},on(fn){这个。arry.push(fn);}}letdata=[];pubsub.on(()=>{if(data.length===3){console.log(data);}});fs.readFile(A,'utf-8',(err,value)=>{data.push(value);pubsub.emit();});fs.readFile(B,'utf-8',(err,value)=>{data.push(value);pubsub.emit();});fs.readFile(C,'utf-8',(err,value)=>{data.push(value);pubsub.emit();});Promise为我们提供了方法Promise.all,对于这个需求,我们可以使用Promise.all来实现。/***将fs.readFile包装成一个promise接口*/functionread(url){returnnewPromise((resolve,reject)=>{fs.readFile(url,'utf8',(err,data)=>{if(err)reject(err);resolve(data);});});}/***使用Promise**通过Promise.all可以实现多个异步并行执行,获取最终结果的问题在同时*/Promise.all([read(A),read(B),read(C)]).then(data=>{console.log(data);}).catch(err=>console.日志(错误));可执行代码可戳:https://github.com/YvetteLau/...3、GeneratorGenerator函数是ES6提供的异步编程解决方案。整个Generator函数就是一个封装好的异步任务,或者说是一个异步任务容器。异步操作需要暂停的地方用yield语句标示。生成器函数通常与yield或Promise一起使用。Generator函数返回一个迭代器。对生成器和迭代器不了解的同学请自行研究基础知识。下面看一下Generator的简单使用:function*gen(){leta=yield111;控制台日志(一);让b=产量222;控制台日志(b);让c=产量333;控制台日志(c);让d=产量444;console.log(d);}lett=gen();//next方法可以带一个参数,作为之前yield表达式的返回值t.next(1);//第一次调用next函数时,传入的参数无效t.next(2);//a输出2;t.next(3);//b输出3;t.下一个(4);//c输出4;t.下一个(5);//d输出5;为了让大家更好的理解上面的代码是如何执行的,我画了一张图片对应每个next方法调用:还是拿上面的readFile(先读取A的文本内容,再根据A的文本内容读取B,然后根据B)的内容读C为例,使用Generator+co库实现:constfs=require('fs');constco=require('co');constbluebird=require('bluebird');constreadFile=bluebird.promisify(fs.readFile);function*read(){yieldreadFile(A,'utf-8');yieldreadFile(B,'utf-8');yieldreadFile(C,'utf-8');//....}co(read()).then(data=>{//code}).catch(err=>{//code});Generator的缺点我就不用多说了,除非你是找虐,否则你也不会直接用Generator来解决异步问题(当然也不排除因为我不精通)~~~如何在不使用co库的情况下实现它?能不能自己写个最简单的my_co,有助于理解async/await的实现原理?请戳:https://github.com/YvetteLau/...PS:如果对Generator/yield不是很了解,建议阅读ES6相关文档4.async/awaitasync/await的概念是在ES7中引入的。Async其实是一个语法糖,它的实现是把Generator函数和自动执行器(co)包装在一个函数中。async/await的好处是代码清晰,不需要像Promise那样写很多then链来处理回调地狱的问题。错误可以是trycatch。还是以上面的readFile(先读取A的文本内容,再根据A的文本内容读取B,再根据B的内容读取C)为例,使用async/await实现:constfs=要求('fs');constbluebird=require('bluebird');constreadFile=蓝鸟。承诺(fs.readFile);asyncfunctionread(){awaitreadFile(A,'utf-8');等待readFile(B,'utf-8');等待readFile(C,'utf-8');//code}read().then((data)=>{//code}).catch(err=>{//code});使用async/await实现这个需求:读取A、B、C三个文件的内容,全部读取成功后输出最终结果。functionread(url){returnnewPromise((resolve,reject)=>{fs.readFile(url,'utf8',(err,data)=>{if(err)reject(err);resolve(data);});});}asyncfunctionreadAsync(){letdata=awaitPromise.all([read(A),read(B),read(C)]);返回数据;}readAsync().then(data=>{console.log(data);});所以JS的异步发展史可以从callback->promise->generator->async/await来考虑。async/await让异步代码看起来像同步代码,异步编程开发的目标就是让异步逻辑代码看起来像同步。由于本人水平有限,文章中的内容可能不是100%正确。如果有什么不对的地方,请给我留言,谢谢。参考文章:[1]详解JavaScript异步函数发展史[2]ES6Promise[3]ES6Generator[4]ES6async[5]JavaScript异步编程点赞和star,你们的肯定是我前进的最大动力。https://github.com/YvetteLau/...推荐关注我公众号:
