js 发布订阅模式解决业务耦合问题

2025-07-20 23:33:36

问题是什么?

前端开发通常会对 axios 网络请求库进行二次封装,对于不同的响应码进行不同的处理。比如 401 的时候跳转到登录页面,403 的时候跳转到权限页面,500 的时候跳转到错误页面。

// request.ts
const request = axios.create()

const successHandler = (res) => {}

const errorHandler = (err) => {
  if (error.response.status === 401) {
    // 跳转登录页
  }
  else if (error.response.status === 403) {
    // 跳转权限页
  }
  else if (error.response.status === 500) {
    // 跳转错误页
  }
  else {
    // ....
  }
}

request.interceptor.response.use(successHandler, errorHandler)

另一个商城项目也需要对 axios 网络请求库进行封装,这个项目对于不同响应码,业务逻辑有所改变。当接收到401状态码时,如果位于商品详情页面,需要弹出对话框进行登录,其它页面则是跳转到登录页进行登录。

// request.ts
const request = axios.create()

const successHandler = (res) => {}

const errorHandler = (err) => {
  if (error.response.status === 401) {
    if (isProductDetailPage()) {
      // 在产品详情页面弹出对话框进行登录
    } else {
      // 其它页面跳转登录页面进行登录
    }
  }
  else {
    // ....
  }
}

request.interceptor.response.use(successHandler, errorHandler)

我们封装的网络请求库内部掺杂不同的业务逻辑,无法复用。

如何解耦合?

如下代码是一个 EventEmitter 类,用于发布订阅。

// eventEmitter.ts

// 事件名称
const eventNames = [ 'API:401', 'API:403', 'API:500' ] as const

// 下面的类型定义相当于: type EventName = 'API:401' | 'API:403' | 'API:500'
type EventName = typeof eventNames[number]

class EventEmitter {
  // 事件监听器,一个事件可以有多个监听器(处理函数)
  private listeners: Record<EventName, Function[]> = {
    'API:401': [],
    'API:403': [],
    'API:500': [],
  }

  /**
   * 注册事件
   */
  public on(eventName: EventName, listener: Function) {
    this.listeners[eventName].push(listener)
  }

  /**
   * 触发事件
   */
  public emit(eventName: EventName, ...args: any[]) {
    this.listeners[eventName].forEach(listener => {
      listener(...args)
    })
  }
}

export default new EventEmitter()

改写上面的网络请求库,接收不同的响应码,触发对应的事件,完全不需要关心具体的具体的业务逻辑是什么!

// request.ts
import eventEmitter from './eventEmitter'

const request = axios.create()

const successHandler = (res) => {}

const errorHandler = (err) => {
  if (error.response.status === 401) {
    eventEmitter.emit('API:401')
  }
  else if (error.response.status === 403) {
    eventEmitter.emit('API:403')
  }
  else if (error.response.status === 500) {
    eventEmitter.emit('API:500')
  }
  else {
    // ....
  }
}

request.interceptor.response.use(successHandler, errorHandler)

经过修改后的网络请求库与业务逻辑解耦合,可以考虑发布成一个 npm 包在不同的项目中使用,比如包的名字叫做 dkvirus-request,其中包含 request.ts 和 eventEmitter.ts 两个文件。

在项目1的入口文件中定义事件的具体实现逻辑:

import { eventEmitter } from 'dkvirus-request'

// 定义监听函数
eventEmitter.on('API:401', () => {
  // 跳转登录页面 
})

eventEmitter.on('API:403', () => {
  // 跳转权限页面 
})

eventEmitter.on('API:500', () => {
  // 跳转错误页面 
})

在项目2的入口文件中定义事件的具体实现逻辑:

import { eventEmitter } from 'dkvirus-request'

// 定义监听函数
eventEmitter.on('API:401', () => {
  if (isProductDetailPage()) {
    // 在产品详情页面弹出对话框进行登录
  } else {
    // 其它页面跳转登录页面进行登录
  }
})

最后

上面贴了 eventEmitter 的简化版代码,实际开发中我们不需要每次都写这个类,npm 仓库里有一个叫做 events 的第三方包,可以直接使用。感兴趣的可以看下它的 源码

返回首页

本文总阅读量  次
皖ICP备17026209号-3
总访问量: 
总访客量: