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

前言

场景:我们在日常项目中,是否遇到过,一个页面内,不同层次的组件(如:父子组件)内都调用了相同接口,导致进入页面,接口就重复多次调用,而你的理想状态是:我进入页面,管你接口在这个页面的什么地方调用,管你这个接口调用了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,欢迎一起讨论,感谢,欢迎提出各种场景,一起探讨

相关推荐
z-robot2 分钟前
Nginx 配置代理
前端
用户47949283569159 分钟前
Safari 中文输入法的诡异 Bug:为什么输入 @ 会变成 @@? ## 开头 做 @ 提及功能的时候,测试同学用 Safari 测出了个奇怪的问题
前端·javascript·浏览器
没有故事、有酒21 分钟前
Ajax介绍
前端·ajax·okhttp
火车叨位去194924 分钟前
软件设计模式(tyutJAVA 状态模式实验)
设计模式·状态模式
朝新_25 分钟前
【SpringMVC】详解用户登录前后端交互流程:AJAX 异步通信与 Session 机制实战
前端·笔记·spring·ajax·交互·javaee
裴嘉靖27 分钟前
Vue 生成 PDF 完整教程
前端·vue.js·pdf
毕设小屋vx ylw28242629 分钟前
Java开发、Java Web应用、前端技术及Vue项目
java·前端·vue.js
冴羽1 小时前
今日苹果 App Store 前端源码泄露,赶紧 fork 一份看看
前端·javascript·typescript
蒜香拿铁1 小时前
Angular【router路由】
前端·javascript·angular.js
brzhang2 小时前
读懂 MiniMax Agent 的设计逻辑,然后我复刻了一个MiniMax Agent
前端·后端·架构