新手如何快速理解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(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。