记录一次开发中按钮切换的竞态问题

525 阅读2分钟

这是我参与8月更文挑战的第26天,活动详情查看:8月更文挑战

前言

今天遇到一个问题,我的项目的场景是,页面上有按钮,按钮可以切换,切换后底下的内容会切换,但是这个内容是请求后台,后台返回的数据。

如果我切换按钮的速度快一些,而且先请求的接口的返回比后请求接口的返回慢一些,就会导致先请求的接口返回的内容展示在后请求的按钮下面,导致内容不一致。

场景复现

下面我用代码模拟一下:

  <div>
    <button>button 1</button>
    <button>button 2</button>
    <button>button 3</button>
  </div>
  <div class="content">content:</div>
  <script>
    const buttonList = document.querySelectorAll('button')
    const contentDom = document.querySelector('.content')
    const content = contentDom.innerText

    buttonList.forEach((item, i) => {
      item.addEventListener('click', () => {
        const index = i + 1
        if (item.innerText === 'button 1') {
          setTimeout(() => {
            freshContent(item, index)
          }, 2000)
        }
        else if (item.innerText === 'button 2') {
          setTimeout(() => {
            freshContent(item, index)
          }, 500)
        } else {
          freshContent(item, index)
        }
      })
    })
    function freshContent(index) {
      contentDom.innerText = content + ' ' + index
    }
  </script>

解析:

这里我用setTimeout模拟接口的返回速度,点击button1,是2000ms后刷新内容,点击button2,是500ms后刷新内容,点击button3,是直接刷新内容。

效果如下:

QQ20210826-234742-HD.gif

可以看到,我先点击的第一个按钮,最后点击的第二个按钮,但是由于第一个按钮的刷新是在2000ms,就是我点击第二个按钮和第二个按钮的内容刷新之后才会触发,导致把第二个按钮的内容给覆盖了,出现了点击第二个按钮,然后展现第一个按钮的内容的情况。

那怎么解决呢?

问题解决

第一种方法

通过记录次数来解决。

每次按钮点击的时候都记录次数(次数加一),然后把当前的次数都存到点击的按钮身上,刷新内容的时候看记录的次数和当前的按钮的次数是不是相等的。如果是相等的,则证明是最后一次触发的,就可以刷新内容。

代码如下:

  <div>
    <button>button 1</button>
    <button>button 2</button>
    <button>button 3</button>
  </div>
  <div class="content">content:</div>
  <script>
    const buttonList = document.querySelectorAll('button')
    const contentDom = document.querySelector('.content')
    const content = contentDom.innerText
    // 记录的次数
    let count = 0
    buttonList.forEach((item, i) => {
      item.addEventListener('click', () => {
        // 次数加一
        count++
        // 把当前次数存到当前button中
        item.count = count
        const index = i + 1
        if (item.innerText === 'button 1') {
          setTimeout(() => {
            freshContent(item, index)
          }, 2000)
        }
        else if (item.innerText === 'button 2') {
          setTimeout(() => {
            freshContent(item, index)
          }, 500)
        } else {
          freshContent(item, index)
        }
      })
    })
    function freshContent(item, index) {
      // 判断当前button的次数和记录的次数是否相等
      if (item.count === count) {
        contentDom.innerText = content + ' ' + index
      }
    }
  </script>

QQ20210827-000737-HD.gif

可以看到不管怎么点,内容都是一致的。

第二种方法

可以通过中断请求来实现。

就是我们在点击下一个按钮的时候,把上一次按钮的请求中断了,XMLHttpRequest可以通过abort方法实现,axios可以通过cancelToken实现。

不过这个方法不确定是否请求已经发送给后台了,所以有可能会中断失败。

第三种方法

可以通过加loading动画展示,直到请求返回,才可点击下一个按钮。

就是我们在点击按钮的时候,发出请求后,加一个loading的动画,然后此时按钮不可点,直到请求成功了,或者失败了,才把loading去掉,按钮才可点击,依此类推。

总结

这个就是我遇到的开发中按钮切换的竞态问题,这种问题很常见,大家记录下来,后面遇到就知道怎么解决了这个问题了。

感谢你们的阅读。