本文已收录在Githubgithub.com/Geekhyt,感谢Star。Node.js异步编程回调我们知道Node.js中有两种事件处理方法,分别是回调(callback)和EventEmitter(事件发射器)。本文首先介绍回调。错误优先回调错误优先是Node.js回调的标准。第一个参数是错误,后面的参数是结果。我们以现实生活中的面试为例。如果面试成功,我们会笑得很开心。如果面试失败了,我们会哭着寻找失败的原因。try{interview(function(){console.log('smile');});}catch(e){console.log('cry',e);}functioninterview(callback){setTimeout(()=>{if(Math.random()<0.1){callback('success');}else{thrownewError('fail');}},500);}上面代码运行后,try/catch不像我们果然没有捕获到错误,而是把错误抛给了Node.js全局,导致程序崩溃。(因为Node.js的每个事件循环都是一个新的调用栈CallStack)为了解决以上问题,Node.js官方形成了如下规范:interview(function(res){if(res){returnconsole.log('cry');}console.log('smile');})functioninterview(callback){setTimeout(()=>{if(Math.random()<0.8){callback(null,'成功');}else{callback(newError('fail'));}},500);}CallbackhellCallbackhellXX大厂面试三轮,看下面?interview(function(err){if(err){returnconsole.log('第一轮哭');}interview(function(err){if(err){returnconsole.log('第二轮哭');}interview(function(err){returnconsole.log('cryat3rdround');})console.log('smile');})})functioninterview(callback){setTimeout(()=>{if(Math.random()<0.1){回调(null,'success');}else{callback(newError('fail'));}},500);}让我们再看看并发条件下回调的表现同时去两家公司面试。当两次面试都成功时,我们会很高兴。看到以下内容了吗?varcount=0;interview(function(err){if(err){returnconsole.log('cry');}count++;})interview(function(err){if(err){returnconsole.log('cry');}count++;if(count){//当count满足一定条件时,所有面试通过//...returnconsole.log('smile');}})functioninterview(callback){setTimeout(()=>{if(Math.random()<0.1){callback(null,'success');}else{callback(newError('fail'));}},500);}异步逻辑的增加随着嵌套深度的增加。上面的代码有很多缺点:代码臃肿,不利于阅读和维护的高耦合。当需求发生变化时,重构成本很高。因为回调函数都是匿名函数,所以很难定位bug。为了解决回调地狱,社区提出了一些解决方案。1、async.jsnpm包是社区早期为解决回调地狱而提出的异步流程控制库。2.thunk编程范式,著名的co模块在v4之前的版本中大量使用了Thunk函数。Redux中还有一个中间件redux-thunk。但都退出了历史舞台。毕竟,软件工程中没有灵丹妙药。替代它们的解决方案是PromisePromisePromise/A+规范构建,ES6使用它来实现Promise。Promise是一种异步编程的解决方案。ES6将其写入语言标准,统一使用,原生提供Promise对象。简单的说,Promise就是当前的事件循环不会得到结果,但是未来的事件循环会给你结果。毫无疑问,无极是个没良心的人。Promise也是一个状态机,只能从pending变成下面的状态(一旦改变,就不能再改变)fulfilled(本文称为resolved)rejected//nodejs不会打印状态//你可以varpromise=newPromise(Chrome控制台中的函数(resolve,reject){setTimeout(()=>{resolve();},500)})console.log(promise);setTimeout(()=>{console.log(promise);},800);//在node.js中//promise{}//promise{}//把上面的代码放到闭包中,丢到googleconsole//ingoogle//Promise{}//Promise{:undefined}Promisethencatchresolved状态下的Promise会回调.thenrejected状态下的第一个Promise后会回调rejected状态下的第一个.catchPromise状态,它后面没有.catch。/node环境的全局错误。Promise优于回调的地方在于它可以解决异步流程控制的问题。(function(){varpromise=interview();promise.then((res)=>{console.log('smile');}).catch((err)=>{console.log('cry');});functioninterview(){returnnewPromise((resoleve,reject)=>{setTimeout(()=>{if(Math.random()>0.2){resolve('success');}else{reject(新错误('失败'));}},500);});}})();执行then和catch会返回一个新的Promise,Promise的最终状态由then和catch的回调函数执行结果决定。我们可以看到如下代码并打印出结果:(function(){varpromise=interview();varpromise2=promise.then((res)=>{thrownewError('refuse');});setTimeout(()=>{console.log(promise);console.log(promise2);},800);functioninterview(){returnnewPromise((resoleve,reject)=>{setTimeout(()=>{如果(Math.random()>0.2){resolve('success');}else{reject(newError('fail'));}},500);});}})();//Promise{:"success"}//Promise{:Error:refuse}如果回调函数最终抛出,则Promise被拒绝。如果回调函数最终返回,则Promise已解决。但是如果回调函数最终返回了一个Promise,这个Promise会和回调函数返回Promise的状态保持一致。Promise解决回调地狱下面我们用Promise在大厂重新实现上面三轮面试的代码。(function(){varpromise=interview(1).then(()=>{returninterview(2);}).then(()=>{returninterview(3);}).then(()=>{console.log('smile');}).catch((err)=>{console.log('cryat'+err.round+'round');});functioninterview(round){returnnewPromise((resolve,reject)=>{setTimeout(()=>{if(Math.random()>0.2){resolve('success');}else{varError=newError('fail');error.round=round;reject(error);}},500);});}})();与回调地狱相比,Promise实现的代码要透明得多。Promise在一定程度上把回调地狱变成了更线性的代码,去掉了横向展开,把回调函数放到了then里,但是在主进程中还是存在的,和我们大脑顺序线性的思维逻辑还是有区别的.Promise处理并发async(function(){Promise.all([interview('阿里巴巴'),interview('腾讯')]).then(()=>{console.log('smile');}).catch((err)=>{console.log('cryfor'+err.name);});functioninterview(name){returnnewPromise((resolve,reject)=>{setTimeout(()=>{if(Math.random()>0.2){resolve('success');}else{varError=newError('fail');error.name=name;reject(error);}},500);});}})();上面代码中的catch是有问题的。请注意,它只会收到第一个错误。GeneratorGenerator和GeneratorFunction是ES6引入的新特性,借鉴了Python、C#等语言。生成器本质上是一种特殊的迭代器。function*doSomething(){}如上图,function后面的“*”就是Generator。函数*doSomething(){面试(1);屈服;//行(A)采访(2);}varperson=doSomething();person.next();//执行interview1,先面试,然后暂停StopatLine(A)person.next();//继续执行(A)行,执行interview2,进行第二次面试next的返回结果第一个人.next()返回结果为{value:'',done:false}第二个人.next()returnresultis{value:'',done:true}关于next的返回结果,我们要知道,如果done的值为true,说明Generator中所有的异步操作都执行完了。为了在Generator中使用多个yield,TJHolowaychuk编写了著名的ES6模块co。co的源码有很多巧妙的实现,大家可以自行阅读。async/awaitGenerator的缺点是没有执行器。它是一个为计算而设计的迭代器,而不是为流量控制而设计的。co的出现较好的解决了这个问题,但是为什么一定要用co而不是直接实现呢?async/await被选为天之骄子,应运而生。异步函数是跨事件循环存在的函数。asyncfunction其实就是对Promise的语法糖封装。它也被称为异步编程的终极解决方案——用同步的方式写异步。await关键字可以“暂停”异步函数的执行。await关键字可以同步获取Promise的执行结果。try/catch可以获取到await获取到的任何错误,解决了上面Promise中的catch只能获取第一个错误的问题。async/await解决回调地狱(asyncfunction(){try{awaitinterview(1);awaitinterview(2);awaitinterview(3);}catch(e){returnconsole.log('cryat'+e.圆);}console.log(“微笑”);})();async/await处理并发async(asyncfunction(){try{awaitPromise.all([interview(1),interview(2)]);}catch(e){returnconsole.log('cryat'+e.圆);}console.log(“微笑”);})();async/await无论是相对于callback还是Promise,都只用几行代码就实现了异步流控。遗憾的是,async/await最终未能进入ES7规范(只能等到ES8),但在ChromeV8引擎中实现了,Node.jsv7.6也集成了async功能。实战经验总结在常见的web应用中,DAO层最好使用Promise,Service层最好使用async函数。参考:狼书-更神奇的Node.jsNode.js开发实践??看完三件事1、看到这里请点赞支持一下。你的喜欢是我创作的动力。2.关注公众号前端食堂,你的前端食堂,记得按时吃饭!3.冬天了,多穿点衣服,别着凉~!