新手如何快速理解js异步编程
前言
异步编程从早期的callback、事件发布\订阅模式到ES6的Promise、Generator在到ES2017中async,看似风格迥异,但是还是有一条暗线将它们串联在一起的,就是希望将异步编程的代码表达尽量地贴合自然语言的线性思维。
以这条暗线将上述几种解决方案连在一起,就可以更好地理解异步编程的原理、魅力。
├──事件发布\订阅模式<=Callback
├──Promise<=事件发布\订阅模式
├──Async、Await<=Promise、Generator
事件发布\订阅模式<=Callback
这个模式本质上就是回调函数的事件化。它本身并无同步、异步调用的问题,我们只是使用它来实现事件与回调函数之间的关联。比较典型的有NodeJS的events模块
const{EventEmitter}=require('events') consteventEmitter=newEventEmitter() //订阅 eventEmitter.on("event",function(msg){ console.log("event",msg) }) //发布 eventEmitter.emit("event","Helloworld")
那么这种模式是如何与Callback关联的呢?我们可以利用Javascript简单实现EventEmitter,答案就显而易见了。
classusrEventEmitter{ constructor(){ this.listeners={} } //订阅,callback为每个event的侦听器 on(eventName,callback){ if(!this.listeners[eventName])this.listeners[eventName]=[] this.listeners[eventName].push(callback) } //发布 emit(eventName,params){ this.listeners[eventName].forEach(callback=>{ callback(params) }) } //注销 off(eventName,callback){ constrest=this.listeners[eventName].fitler(elem=>elem!==callback) this.listeners[eventName]=rest } //订阅一次 once(eventName,callback){ consthandler=function(){ callback() this.off(eventName,handler) } this.on(eventName,handler) } }
上述实现忽略了很多细节,例如异常处理、多参数传递等。只是为了展示事件订阅\发布模式。
很明显的看出,我们使用这种设计模式对异步编程做了逻辑上的分离,将其语义化为
//一些事件可能会被触发 eventEmitter.on //当它发生的时候,要这样处理 eventEmitter.emit
也就是说,我们将最初的Callback变成了事件监听器,从而优雅地解决异步编程。
Promise<=事件发布\订阅模式
使用事件发布\订阅模式时,需要我们事先严谨地设置目标,也就是上面所说的,必须要缜密地设定好有哪些事件会发生。这与我们语言的线性思维很违和。那么有没有一种方式可以解决这个问题,社区产出了Promise。
constpromise=newPromise(function(resolve,reject){
try{
setTimeout(()=>{
resolve('helloworld')
},500)
}catch(error){
reject(error)
}
})
//语义就变为先发生一些异步行为,then我们应该这么处理 promise.then(msg=>console.log(msg)).catch(error=>console.log('err',error))
那么这种Promise与事件发布\订阅模式有什么联系呢?我们可以利用EventEmitter来实现Promise,这样可能会对你有所启发。
我们可以将Promise视为一个EventEmitter,它包含了{state:'pending'}来描述当前的状态,同时侦听它的变化
- 当成功时{state:'fulfilled'},要做些什么on('resolve',callback);
- 当失败时{state:'rejected'},要做些什么on('reject',callback)。
具体实现如下
const{EventEmitter}=require('events') classusrPromiseextendsEventEmitter{ //构造时候执行 constructor(executor){ super() //发布 constresolve=(value)=>this.emit('resolve',value) constreject=(reason)=>this.emit('reject',reason) if(executor){ //模拟eventloop,注此处利用Macrotask来模拟Microtask setTimeout(()=>executor(resolve,reject)) } } then(resolveHandler,rejectHandler){ constnextPromise=newusrPromise() //订阅resolve事件 if(resolveHandler){ constresolve=(data)=>{ constresult=resolveHandler(data) nextPromise.emit('resolve',result) } this.on('resolve',resolve) } //订阅reject事件 if(rejectHandler){ constreject=(data)=>{ constresult=rejectHandler(data) nextPromise.emit('reject',result) } this.on('reject',reject) }else{ this.on('reject',(data)=>{ promise.emit('reject',data) }) } returnnextPromise } catch(handler){ this.on('reject',handler) } }
我们使用then方法来将预先需要定义的事件侦听器存放起来,同时在executor中设定这些事件该在什么时候实行。
可以看出从事件发布\订阅模式到Promise,带来了语义上的巨大变革,但是还是需要使用newPromise来描述整个状态的转换,那么有没有更好地实现方式呢?
async、await<=Promise、Generator
async、await标准是ES2017引入,提供一种更加简洁的异步解决方案。
asyncfunctionsay(greeting){ returnnewPromise(function(resolve,then){ setTimeout(function(){ resolve(greeting) },1500) }) } ;(asyncfunction(){ letv1=awaitsay('Hello') console.log(v1) letv2=awaitsay('World') console.log(v2) })()
await可以理解为暂停当前asyncfunction的执行,等待Promise处理完成。。若Promise正常处理(fulfilled),其回调的resolve函数参数作为await表达式的值。
async、await的出现,减少了多个then的链式调用形式的代码。下面我们结合Promise与Generator来实现async、await
functionasync(makeGenerator){ returnfunction(){ constgenerator=makeGenerator.apply(this,arguments) functionhandle({value,done}){ if(done===true)returnPromise.resolve(value) returnPromise.resolve(value).then( (res)=>{ returnhandle(generator.next(res)) }, function(err){ returnhandle(generator.throw(err)) } ) } try{ returnhandle(generator.next()) }catch(ex){ returnPromise.reject(ex) } } } async(function*(){ varv1=yieldsay('hello') console.log(1,v1) varv2=yieldsay('world') console.log(2,v2) })()
本质上就是利用递归完成function*(){...}的自动执行。相比与Generator函数,这种形式无需手动执行,并且具有更好的语义。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。