后面一篇文章setTimeout和setImmediate到底谁先执行,本文让你彻底了解Event Loop具体解说了浏览器和Node.js的异步API及其底层原理Event Loop。本文会讲一下不必原生API怎么达到异步的成果,也就是公布订阅模式。公布订阅模式在面试中也是高频考点,本文会本人实现一个公布订阅模式,弄懂了他的原理后,咱们就能够去读Node.js的EventEmitter
源码,这也是一个典型的公布订阅模式。
本文所有例子曾经上传到GitHub,同一个repo上面还有我所有博文和例子:
https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/DesignPatterns/PubSub
为什么要用公布订阅模式
在没有Promise
之前,咱们应用异步API的时候常常会应用回调,然而如果有几个相互依赖的异步API调用,回调层级太多可能就会陷入“回调天堂”。上面代码演示了如果咱们有三个网络申请,第二个必须等第一个完结能力收回,第三个必须等第二个完结能力发动,如果咱们应用回调就会变成这样:
const request = require("request");request('https://www.baidu.com', function (error, response) { if (!error && response.statusCode == 200) { console.log('get times 1'); request('https://www.baidu.com', function(error, response) { if (!error && response.statusCode == 200) { console.log('get times 2'); request('https://www.baidu.com', function(error, response) { if (!error && response.statusCode == 200) { console.log('get times 3'); } }) } }) }});
因为浏览器端ajax会有跨域问题,上述例子我是用Node.js运行的。这个例子外面有三层回调,咱们曾经有点晕了,如果再多几层,那真的就是“天堂”了。
公布订阅模式
公布订阅模式是一种设计模式,并不仅仅用于JS中,这种模式能够帮忙咱们解开“回调天堂”。他的流程如下图所示:
- 音讯核心:负责存储音讯与订阅者的对应关系,有音讯触发时,负责告诉订阅者
- 订阅者:去音讯核心订阅本人感兴趣的音讯
- 发布者:满足条件时,通过音讯核心公布音讯
有了这种模式,后面解决几个相互依赖的异步API就不必陷入"回调天堂"了,只须要让前面的订阅后面的胜利音讯,后面的胜利后公布音讯就行了。
本人实现一个公布订阅模式
晓得了原理,咱们本人来实现一个公布订阅模式,这次咱们应用ES6的class来实现,如果你对JS的面向对象或者ES6的class还不相熟,请看这篇文章:
class PubSub { constructor() { // 一个对象寄存所有的音讯订阅 // 每个音讯对应一个数组,数组构造如下 // { // "event1": [cb1, cb2] // } this.events = {} } subscribe(event, callback) { if(this.events[event]) { // 如果有人订阅过了,这个键曾经存在,就往里面加就好了 this.events[event].push(callback); } else { // 没人订阅过,就建一个数组,回调放进去 this.events[event] = [callback] } } publish(event, ...args) { // 取出所有订阅者的回调执行 const subscribedEvents = this.events[event]; if(subscribedEvents && subscribedEvents.length) { subscribedEvents.forEach(callback => { callback.call(this, ...args); }); } } unsubscribe(event, callback) { // 删除某个订阅,保留其余订阅 const subscribedEvents = this.events[event]; if(subscribedEvents && subscribedEvents.length) { this.events[event] = this.events[event].filter(cb => cb !== callback) } }}
解决回调天堂
有了咱们本人的PubSub
,咱们就能够用它来解决后面的回调天堂问题了:
const request = require("request");const pubSub = new PubSub();request('https://www.baidu.com', function (error, response) { if (!error && response.statusCode == 200) { console.log('get times 1'); // 公布申请1胜利音讯 pubSub.publish('request1Success'); }});// 订阅申请1胜利的音讯,而后发动申请2pubSub.subscribe('request1Success', () => { request('https://www.baidu.com', function (error, response) { if (!error && response.statusCode == 200) { console.log('get times 2'); // 公布申请2胜利音讯 pubSub.publish('request2Success'); } });})// 订阅申请2胜利的音讯,而后发动申请3pubSub.subscribe('request2Success', () => { request('https://www.baidu.com', function (error, response) { if (!error && response.statusCode == 200) { console.log('get times 3'); // 公布申请3胜利音讯 pubSub.publish('request3Success'); } });})
Node.js的EventEmitter
Node.js的EventEmitter
思维跟咱们后面的例子是一样的,不过他有更多的错误处理和更多的API,源码在GitHub上都有:https://github.com/nodejs/node/blob/master/lib/events.js。咱们挑几个API看一下:
构造函数
代码传送门: https://github.com/nodejs/node/blob/master/lib/events.js#L64
构造函数很简略,就一行代码,次要逻辑都在EventEmitter.init
外面:
EventEmitter.init
外面也是做了一些初始化的工作,this._events
跟咱们本人写的this.events
性能是一样的,用来存储订阅的事件。外围代码我在图上用箭头标出来了。这里须要留神一点,如果一个类型的事件只有一个订阅,this._events
就间接是那个函数了,而不是一个数组,在源码外面咱们会屡次看到对这个进行判断,这样写是为了进步性能。
订阅事件
代码传送门: https://github.com/nodejs/node/blob/master/lib/events.js#L405
EventEmitter
订阅事件的API是on
和addListener
,从源码中咱们能够看出这两个办法是齐全一样的:
这两个办法都是调用了_addListener
,这个办法对参数进行了判断和错误处理,外围代码依然是往this._events
外面增加事件:
公布事件
代码传送门:https://github.com/nodejs/node/blob/master/lib/events.js#L263
EventEmitter
公布事件的API是emit
,这个API外面会对"error"类型的事件进行非凡解决,也就是抛出谬误:
如果不是谬误类型的事件,就把订阅的回调事件拿进去执行:
勾销订阅
代码传送门:https://github.com/nodejs/node/blob/master/lib/events.js#L450
EventEmitter
外面勾销订阅的API是removeListener
和off
,这两个是齐全一样的。EventEmitter
的勾销订阅API不仅仅会删除对应的订阅,在删除后还会emit一个removeListener
事件来告诉外界。这里也会对this._events
外面对应的type
进行判断,如果只有一个,也就是说这个type
的类型是function
,会间接删除这个键,如果有多个订阅,就会找出这个订阅,而后删掉他。如果所有订阅都删完了,就间接将this._events
置空:
总结
本文解说了公布订阅模式的原理,并本人实现了一个简略的公布订阅模式。在理解了原理后,还去读了Node.js的EventEmitter
模块的源码,进一步学习了生产环境的公布订阅模式的写法。总结下来公布订阅模式有以下特点:
- 解决了“回调天堂”
- 将多个模块进行理解耦,本人执行时,不须要晓得另一个模块的存在,只须要关怀公布进去的事件就行
- 因为多个模块能够不晓得对方的存在,本人关怀的事件可能是一个很边远的旮旯公布进去的,也不能通过代码跳转间接找到公布事件的中央,debug的时候可能会有点艰难。
文章的最初,感激你破费贵重的工夫浏览本文,如果本文给了你一点点帮忙或者启发,请不要悭吝你的赞和GitHub小星星,你的反对是作者继续创作的能源。
作者博文GitHub我的项目地址: https://github.com/dennis-jiang/Front-End-Knowledges