玩玩JS设计模式之:发布/订阅

栏目: 后端 · 发布时间: 6年前

内容简介:Node.js的events 模块功能强大,除了常规的监听、触发,还支持事件顺序(prependListener),本文只是写着玩玩,真正要用的话,还是选择成熟稳定的东西较好!内容概览: 以下订阅=监听、发布=触发;一般来说,四个功能:

Node.js的events 模块功能强大,除了常规的监听、触发,还支持事件顺序(prependListener),本文只是写着玩玩,真正要用的话,还是选择成熟稳定的东西较好!

内容概览: 以下订阅=监听、发布=触发;一般来说, 先订阅事件再发布事件 ;就像打电话一样,电话没拨通(订阅),你就开始说话要干嘛干嘛(发布),这时候订阅是无效的!!!因为触发在前、监听在后,触发的时候没有监听,监听的时候已经结束, 二者不在一个频道!!! 沟通就是无效的。。。

四个功能:

  • 订阅on
  • 订阅once(一次性
  • 发布emit
  • 注销off
  • 错误监听error

构造函数

// 发布订阅,回调函数版本
function EvtEmit() {
  // 事件参数队列
  this.evtList = [];
}
复制代码

原型

EvtEmit.prototype = {
  constructor: EvtEmit,
  // 订阅事件(监听)
  on(emitName, handler) {
    // console.debug(`EvtEmit -- on: ${emitName}`);
    if (!emitName) return;
    if (!this.evtList.some(evt => evt.emitName === emitName)) {
      this.evtList.push({
        emitName, // 事件名称
        handler,
        once: emitName === 'error' ? true : false
      });
    }
  },
  // 订阅事件(一次性
  once(emitName, handler) {
    if (!emitName) return;
    if (!this.evtList.some(evt => evt.emitName === emitName)) {
      this.evtList.push({
        emitName, // 事件名称
        handler,
        once: true
      });
    }
  },
  // 发布事件(触发)
  emit(emitName, ...param) {
    // console.debug(`EvtEmit -- emit: ${emitName}`);
    if (!emitName) return;
    let evtThis = this.evtList.find(evt => evt.emitName === emitName);
    if (!evtThis) {
      if (emitName !== 'error') {
        console.warn(`请先使用监听on('${emitName}', callback),再emit('${emitName}')!`);
      }
      return;
    }
    // 一次性订阅
    if (evtThis.once) this.off(emitName);

    // 监听[emitName]回调的错误
    try {
      evtThis.handler(...param);
    }
    catch (err) {
      // 不使用 on('error', callback)监听时,打印错误
      if (!this.evtList.some(evt => evt.emitName === 'error')) {
        console.error(`on('${emitName}', callback) -> callback Error: ${err}`);
      }

      // 错误触发,可以使用 on('error', callback)监听
      this.emit('error', {
        emitName,
        err
      });
    }
  },
  // 注销事件订阅
  off(emitName, callback = null) {
    let arr = this.evtList.filter(evt => evt.emitName !== emitName);
    this.evtList = arr;
    arr = null;
    if (callback) {
      callback.call(this, emitName);
    }
  }
};

复制代码

使用

同正常的发布订阅一样,先订阅(on)再发布(emit);

const evt = new EvtEmit();

// 监听'run'事件
// 执行1次on监听,10次回调函数
evt.on('run', res => {
  console.log('res: ', res);
  // 注销监听,以下 emit 之后将不再触发 on;注释之后将无限调用
  if (--res < 1) {
    evt.off('run', emitName => {
      console.log(`on('${emitName}')已注销!`);
    });
    return;
  }

  evt.emit('run', res);
});

evt.emit('run', 10);


// 监听'go'事件
// 一秒执行回调
evt.on('go', res => {
  console.log('res: ', res);
});

// 1秒后订阅'go'事件
setTimeout(() => {
  evt.emit('go', 'go');
}, 1000);

复制代码

输出

玩玩JS设计模式之:发布/订阅

错误监听

try/catch 执行监听的回调函数,捕获错误然后触发 emit('error', err) ,通过 on('error', callback) 监听错误;

为什么需要 try/catch?

不使用try/catch捕获错误的话,一旦发生错误,进程就挂了,这时,后续不需要依赖你这次操作结果的 程序就会跑不下去了!!!(如下 打印 'after go',如果没有try/catch,那么他就不会被打印);

这在服务端用的比较多,想象一下,一个接口因为某次调用的参数不合法或者其他因素,导致程序中断而影响到后续使用,可能产生‘事故’!

try/catch 包裹回调函数的执行

// 发布事件(触发)
  emit(emitName, ...param) {
    // console.debug(`EvtEmit -- emit: ${emitName}`);
    if (!emitName) return;
    let evtThis = this.evtList.find(evt => evt.emitName === emitName);
    if (!evtThis) {
      if (emitName !== 'error') {
        console.warn(`请先使用监听on('${emitName}', callback),再emit('${emitName}')!`);
      }
      return;
    }
    // 一次性订阅
    if (evtThis.once) this.off(emitName);

    // 监听[emitName]回调的错误
    try {
      evtThis.handler(...param);
    }
    catch (err) {
      // 不使用 on('error', callback)监听时,打印错误
      if (!this.evtList.some(evt => evt.emitName === 'error')) {
        console.error(`on('${emitName}', callback) -> callback Error: ${err}`);
      }

      // 错误触发,可以使用 on('error', callback)监听
      this.emit('error', {
        emitName,
        err
      });
    }
  },
复制代码

错误捕获:

evt.on('error', ({ emitName, err }) => {
  console.error(`on('${emitName}', callback) -> callback Error: ${err}`);
})

evt.on('go', res => {
  err; // 错误会被 try/catch 捕获
  console.log('res: ', res);
});

evt.emit('go', 'go');
console.log('after go'); // 没有 try/catch 的话,不会执行
复制代码

全部代码 EvtEmit_callback.js:

// EvtEmit_callback.js
// 发布订阅,回调函数版本
function EvtEmit() {
  // 事件参数队列
  this.evtList = [];
}
EvtEmit.prototype = {
  constructor: EvtEmit,
  // 订阅事件(监听)
  on(emitName, handler) {
    // console.debug(`EvtEmit -- on: ${emitName}`);
    if (!emitName) return;
    if (!this.evtList.some(evt => evt.emitName === emitName)) {
      this.evtList.push({
        emitName, // 事件名称
        handler,
        once: emitName === 'error' ? true : false
      });
    }
  },
  // 订阅事件(一次性
  once(emitName, handler) {
    if (!emitName) return;
    if (!this.evtList.some(evt => evt.emitName === emitName)) {
      this.evtList.push({
        emitName, // 事件名称
        handler,
        once: true
      });
    }
  },
  // 发布事件(触发)
  emit(emitName, ...param) {
    // console.debug(`EvtEmit -- emit: ${emitName}`);
    if (!emitName) return;
    let evtThis = this.evtList.find(evt => evt.emitName === emitName);
    if (!evtThis) {
      if (emitName !== 'error') {
        console.warn(`请先使用监听on('${emitName}', callback),再emit('${emitName}')!`);
      }
      return;
    }
    // 一次性订阅
    if (evtThis.once) this.off(emitName);

    // 监听[emitName]回调的错误
    try {
      evtThis.handler(...param);
    }
    catch (err) {
      // 不使用 on('error', callback)监听时,打印错误
      if (!this.evtList.some(evt => evt.emitName === 'error')) {
        console.error(`on('${emitName}', callback) -> callback Error: ${err}`);
      }

      // 错误触发,可以使用 on('error', callback)监听
      this.emit('error', {
        emitName,
        err
      });
    }
  },
  // 注销事件订阅
  off(emitName, callback = null) {
    let arr = this.evtList.filter(evt => evt.emitName !== emitName);
    this.evtList = arr;
    arr = null;
    if (callback) {
      callback.call(this, emitName);
    }
  }
};

const evt = new EvtEmit();

// 执行1次on监听,10次回调函数
evt.on('run', res => {
  console.log('res: ', res);
  // 注销监听,以下 emit 之后将不再触发 on;注释之后将无限调用
  if (--res < 1) {
    evt.off('run', emitName => {
      console.log(`on('${emitName}')已注销!`);
    });
    return;
  }

  evt.emit('run', res);
});

evt.emit('run', 10);


// evt.on('error', ({ emitName, err }) => {
//   console.error(`on('${emitName}', callback) -> callback Error: ${err}`);
// })

// evt.on('go', res => {
//   err; // 错误会被 try/catch 捕获
//   console.log('res: ', res);
// });

// evt.emit('go', 'go');
// console.log('after go'); // 没有 try/catch 的话,不会执行

复制代码

参考

  1. js设计模式之发布/订阅模式模式
  2. events(事件触发器)

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Node.js in Action

Node.js in Action

Mike Cantelon、Marc Harter、TJ Holowaychuk、Nathan Rajlich / Manning Publications / 2013-11-25 / USD 44.99

* Simplifies web application development * Outlines valuable online resources * Teaches Node.js from the ground up Node.js is an elegant server-side JavaScript development environment perfect for scal......一起来看看 《Node.js in Action》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具