AOP design in front-end framework

AOP design in front-end framework

What is AOP

AOP is a programming paradigm oriented to the business process aspect. The idea of ​​using AOP can isolate each business logic module, reduce the coupling between each module and increase the reusability. AOP does not specify the specific implementation of its protocol. Realize your own needs.

For example, common front-end global filters, request back-end interfaces, global data mapping, intermediate state processing, etc., abstract a certain stage of the linear business logic as an independent module, and cut into more call chains when appropriate In the road

Insert picture description here

How to realize the AOP idea in the front-end framework

Take an http request as an example. For example, the following code includes the processing of the request header, the conversion of the data, the addition of the requested domain name, and the processing of the response.

async function requestAPI(url: string, init: RequestInit) {
    
    
  init.headers = init.headers ?? {
    
    }
  init.headers = {
    
     'contentType': 'application/x-www-form-urlencoded; charset=UTF-8', ...init.headers }
  if (init.body === 'object' && !Array.isArray(init.body)) {
    
    
    init.body = JSON.stringify(init.body ?? {
    
    })
  }
  const regex = new RegExp('(^https:)|(^http:)\/\/([^\/\?]+)', 'g')
  if (!regex.test(url)) {
    
    
    url = 'https:xxx.com' + url
  }
  try {
    
    
    const response = await fetch(url, init)
    const res = await response.json()
    if (res.code === 200) {
    
    
      return res.data
    } else {
    
    
      throw new ApiException({
    
    
        code: res.code,
        msg: res.msg,
        url
      })
    }
  } catch (error) {
    
    
    if (error instanceof ApiException) {
    
    
      throw error
    }
    // ...处理异常
  }
}

In this way of writing, if the request header needs to be adjusted, and some processing of request parameters or responses is required, a new request method needs to be constructed or some modifications to the original method are required, and processing such as throttling and anti-shake is also needed. If this function is modified or encapsulated here, how should it be implemented with the idea of ​​AOP?


interface HttpOption extends RequestInit {
    
    
  url: string
  body?: any
}

interface HttpResponse<T> {
    
    
  code: number
  data: T
  msg: string
}

function DomainDecorators() {
    
    
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    
    
    const fn = descriptor.value as (options: HttpOption) => Promise<Response>
    descriptor.value = (options: {
    
    
      domain: string
    } & HttpOption) => {
    
    
      if (options?.domain) {
    
    
        const regex = new RegExp('(^https:)|(^http:)\/\/([^\/\?]+)', 'g')
        if (!regex.test(options.url)) {
    
    
          options.url = options.domain + options.url
        }
      }
      return fn.call(target, options)
    }
  }
}

function OptionsDecorators() {
    
    
  return function (target: HttpClient, propertyKey: string, descriptor: PropertyDescriptor) {
    
    
    const fn = descriptor.value as (options: HttpOption) => Promise<Response>
    descriptor.value = (options: HttpOption) => {
    
    
      options.headers = {
    
     'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', ...options.headers }
      if (options.body === 'object' && !Array.isArray(options.body)) {
    
    
        options.body = JSON.stringify(options.body ?? {
    
    })
      }
      return fn.call(target, options)
    }
  }
}

function ExceptionDecorators() {
    
    
  return function (target: HttpClient, propertyKey: string, descriptor: PropertyDescriptor) {
    
    
    const fn = descriptor.value as (options: HttpOption) => Promise<HttpResponse<any>>
    descriptor.value = async (options: HttpOption) => {
    
    
      const res = await fn.call(target, options)
      if (res.code === 200) {
    
    
        return res.data
      } else {
    
    
        throw new ApiException({
    
    
          code: res.code,
          msg: res.msg,
          url: options.url
        })
      }
    }
  }
}


abstract class HttpBase {
    
    
  get options(): {
    
    
    domain?: string
  } & RequestInit {
    
    
    return {
    
    
      mode: 'cors',
      credentials: 'include',
      cache: 'no-cache',
      redirect: 'follow'
    }
  }
  post<T>(options: HttpOption): Promise<HttpResponse<T>> {
    
    
    options.method = 'POST'
    return fetch(options.url, options).then((res) => res.json())
  }
  get<T>(options: HttpOption): Promise<HttpResponse<T>> {
    
    
    options.method = 'GET'
    return fetch(options.url, options).then((res) => res.json())
  }
}

class HttpClient extends HttpBase {
    
    
  @DomainDecorators()
  @OptionsDecorators()
  @ExceptionDecorators()
  post<T>(options: HttpOption): Promise<HttpResponse<T>> {
    
    
    return super.post(options)
  }
}

Here we can abstract the request header processing part and the corresponding part, as an aspect weaves into the implemented request class, separate the parameter processing, domain name addition, and response exceptions, and assemble them when needed. Each module is It serves the request, but there is no association or dependency before, as long as the method signature of the decorated object is the same, these functions are reused~

Guess you like

Origin blog.csdn.net/vipshop_fin_dev/article/details/113126337