前端 页面不同组件内调用相同接口导致重复调用,如何只触发一次而不是触发多次

前言

场景:我们在日常项目中,是否遇到过,一个页面内,不同层次的组件(如:父子组件)内都调用了相同接口,导致进入页面,接口就重复多次调用,而你的理想状态是:我进入页面,管你接口在这个页面的什么地方调用,管你这个接口调用了N次,你给我只执行请求一次,有结果,所有相同请求的地方都用这个结果,如果请求失败,那么大家一起失败

我们依然延续之前的文章风格, 场景=>方案=>示例 的模式加注释

方案

我们需要一个 异步同源 对象来支撑我们的场景,具体作用就是进行请求任务的拦截和结果处理

js 复制代码
// 异步同源对象
const asyncSameSource = {
  map: new Map(), // 任务对象
  waitMap: new Map(), // 等待map对象
  // 缓存中是否有该任务
  has (name) {
    return this.map.has(name)
  },
  // 从缓存中获取该任务的结果
  get (name) {
    return this.map.get(name)
  },
  // 缓存该任务的结果
  set (name, value) {
    this.map.set(name, value)
  },
  // 删除该任务的结果
  delete (name) {
    this.map.delete(name)
  },
  // 添加等待该任务结果的函数
  wait (name, resolve, reject) {
    const waits = this.waitMap.get(name) || []
    waits.push({ resolve, reject })
    this.waitMap.set(name, waits)
  },
  // 派发等待该任务结果的函数
  dispatchWait (name, val, isError = false) {
    if (this.waitMap.has(name)) {
      const waits = this.waitMap.get(name)
      for (let i = 0; i < waits.length; i++) {
        const wait = waits[i]
        isError ? wait.reject(val) : wait.resolve(val)
      }
    }
  },
  // 获取结果函数
  getResult (
    name, // 任务名字,字符串类型
    requestApi, // 请求接口,返回是一个promise
    cache = false // 是否缓存结果
  ) {
    // 任务名字
    const TaskName = `${name}TaskName`
    // 出错任务名字
    const TaskErrName = `${name}TaskErrName`
    // 如果缓存中没有这个任务,同时这个任务也没有出错
    if (!this.has(name) && !this.has(TaskName)) {
        console.log('%c [ 第一个任务 ]-1354', 'font-size:13px; background:pink; color:#bf2c9f;', )
        // 那么对该次任务进行状态标记,关闭 if 入口,让后面相同任务进入 else 入口
        this.set(TaskName, true)
        // 开始请求数据
        return requestApi()
            .then(result => {   // 成功请求数据后
                // 将成功返回的数据结果设置到缓存中
                this.set(name, result)
                // 触发等待任务列表,进行通知,当然是成功的通知,一对都对
                this.dispatchWait(name, result)
                // 返回结果
                return result
            })
            .catch(err => { // 失败
                // 缓存错误任务
                this.set(TaskErrName, err)
                // 触发等待任务列表,进行通知, 当然是失败的通知,一错都错
                this.dispatchWait(name, err, true)
                // 抛出错误
                throw err
            }).finally(() => {
                // 成功失败与否,都已经出了结果,就把缓存标记状态删除,任务结束
                this.delete(TaskName)
                // 如果不缓存结果, 缓存1秒后删除 任务 和 错误任务
                if (!cache) {
                    // 如果不缓存结果,在1000毫秒内再次发起的相同任务依然使用缓存结果
                    setTimeout(() => {
                        this.delete(name)
                        this.delete(TaskErrName)
                    }, 1000)
                }
            })
    } else {
        // 如果同个任务已经在在缓存中了,就走到这里
        return new Promise((resolve, reject) => {
            // 先判断缓存中是否有任务结果
            if (this.has(name)) {
                // 有就直接返回结果
                resolve(this.get(name))
            } else if (this.has(TaskErrName)) { // 再判断缓存中是否有该次任务的错误任务
                // 有就直接返回错误
                reject(this.get(TaskErrName))
            } else {
                // 没有就缓存等待任务,等待任务列表中,有结果了,就通知等待任务列表
                this.wait(name, resolve, reject)
                console.log('%c [ 等待任务 ]-1398', 'font-size:13px; background:pink; color:#bf2c9f;', this.waitMap)
            }
        })
    }
  },
  // 清空任务列表
  clear () {
    this.map.clear()
  }
}

我们 发现 和 上篇 文章 前端 同时多次调用同个接口,如何只触发一次而不是触发多次 有异曲同工之妙,都是用 promise 为基础 用resolve 进行操作,下面我们来看下示例

示例

问题示例

js 复制代码
// api请求 模拟
const api = {
    getUserInfo(){
        console.log('调用 getUserInfo 接口')
        return new Promise((resolve, reject)=>{
            setTimeout(()=>{
                resolve('用户信息')
            },1000)
        })
    },
    getProductInfo(){
        return new Promise((resolve, reject)=>{
            setTimeout(()=>{
                reject('错误')
            },300)
        })
    }
}
 // 页面 3个 地方 调用了相同接口 3次
api.getUserInfo().then(res=>{
    console.log('第一个相同请求', res)
})

api.getUserInfo().then(res=>{
    console.log('第二个相同请求', res)
})

api.getUserInfo().then(res=>{
    console.log('第三个相同请求', res)
})

结果如图所示

我们模拟api 请求,在页面 执行了 3次相同接口,事实上这个接口也确实执行了 3次,如何优化,看我们下面进一步操作

优化示例

js 复制代码
// api请求 模拟

const api = {
    getUserInfo(...args){
        const requestUserInfoApi = (..._args)=> {
            console.log('%c [ 调用 getUserInfo 接口 ]-1235', 'font-size:13px; background:pink; color:#bf2c9f;', _args)
            return new Promise((resolve, reject)=>{
                setTimeout(()=>{
                    resolve('用户信息')
                },1000)
            })
        }
        return asyncSameSource.getResult('getUserInfo', ()=>requestUserInfoApi(...args),true)
    },
    getProductInfo(...args){
        const requestProductInfoApi = (..._args)=> {
            console.log('%c [ 调用 getProductInfo 接口 ]-1235', 'font-size:13px; background:pink; color:#bf2c9f;', _args)
            return new Promise((resolve, reject)=>{
                setTimeout(()=>{
                    reject('商品信息')
                },1000)
            })
        }
        return asyncSameSource.getResult('getProductInfo', ()=>requestProductInfoApi(...args),true)
    }
}

// 页面 3个 地方 调用了相同接口 3次

api.getUserInfo(1).then(res=>{
    console.log('第一个相同请求', res)
})

api.getUserInfo(2).then(res=>{
    console.log('第二个相同请求', res)
})

api.getUserInfo(3).then(res=>{
    console.log('第三个相同请求', res)
})

打印结果如下

可以看到 getUserInfo 接口值调用了一次,3个相同请求也都拿到了结果,符合我们的期望,具体项目中,在 我们封装请求的源头 get、post 进行包装一层是最好的,下面我只是象征性的举个样板例子

实际项目中,我们从源头请求进行处理,示例如下

某某页面

js 复制代码
// 象征性的 标识基于 axios 封装过的  请求http
import http from './lib/axios'

/**
 * post方法
 * @param {String} url 请求的url地址
 * @param {Object} params 请求时携带的参数
 * @param {Object} config 额外请求配置参数,便于拦截器里使用
 */
export function post (url, params = {}, config = {}) {
  return asyncSameSource.getResult(url + JSON.stringify(params), () => http
    .post(url, params, config)
    .then(res => {
      if (res && res.data) {
        return res.data
      } else {
        throw new Error(res)
      }
    }))
}

这样就不用在 不用的api接口里面去处理,直接源头处理更为直接

结语

本篇文章是对 前端 同时多次调用同个接口,如何只触发一次而不是触发多次 文章的 会敲代码的柯基 给出的场景而写, 我们项目中出现该场景的情况还是比较多的,希望对大家有用,如有疑问,欢迎留言,有更好的 idea,欢迎一起讨论,感谢,欢迎提出各种场景,一起探讨

相关推荐
大数据追光猿10 分钟前
Python中的Flask深入认知&搭建前端页面?
前端·css·python·前端框架·flask·html5
莫忘初心丶13 分钟前
python flask 使用教程 快速搭建一个 Web 应用
前端·python·flask
横冲直撞de44 分钟前
前端接收后端19位数字参数,精度丢失的问题
前端
我是哈哈hh1 小时前
【JavaScript进阶】作用域&解构&箭头函数
开发语言·前端·javascript·html
摸鱼大侠想挣钱1 小时前
ActiveX控件
前端
谢尔登1 小时前
Vue 和 React 响应式的区别
前端·vue.js·react.js
酷酷的阿云1 小时前
Vue3性能优化必杀技:useDebounce+useThrottle+useLazyLoad深度剖析
前端·javascript·vue.js
神明木佑1 小时前
HTML 新手易犯的标签属性设置错误
前端·css·html
老友@1 小时前
OnlyOffice:前端编辑器与后端API实现高效办公
前端·后端·websocket·编辑器·onlyoffice
bin91531 小时前
DeepSeek 助力 Vue 开发:打造丝滑的缩略图列表(Thumbnail List)
前端·javascript·vue.js·ecmascript·deepseek