用js实现可终止的轮询请求

大家好,我是小黑。

最近遇到了一个需求,需要每隔5s请求一个接口获取接口返回的结果,返回成功后停止请求,接口的返回的值有下面几种情况:

// 成功
{
    "code": 0,
    "msg": '成功'
}
// 查询中
{
    "code": -1,
    "msg": '结果查询中'
}
// 失败
{
    "code": 1,
    "msg": '返回结果失败'
}

code是接口的状态码,为0的时element是什么意思候表示接口接口crc错误计数返回的成功,这时就不需要再请求接口。

实现这种需求首先想到的就是用轮询去做了。

什么是轮询请求?

通俗地说,轮询请求就是间隔相同的时间(如5s)后不断地向服务端发起http 500同一个接口的请求,当然不能无限次去请求,所以轮询必须要有个停止轮询的机制

轮询的要点

1. 按照需要选择是否立即发起请求再进入轮询

2. 上一次的请求响应后到了指定的时间间隔后再继续执行下一次请求

3. 当轮询的请求发生报错的时候应该停止轮询

4. 被停止的轮询可以根据需要再次发起轮询

setInterval的问题

因为是不断请求,所以首先能想到的就是用sethttp 404Interval去实现,但是setInterval做轮询的话,会有以下严重的问题

1. 即使调用的代码报错了,setInterval会持续的调http 404

2. setInterval不会等到上一次的请求响应后再执行,只要到了指定的时间间隔就会执行,哪怕有报错也会执行

3. setInterval定时不准确,这个跟事件循环有关,这里不展开啦~

实现轮询

实现轮询,只要按照轮询的要点去实现一个setInterval方法就可以了,下面用setTimeout一步步去实现这个方法

准备工作

首先准备一个html模板element翻译,这个模httpwatch板包含两个按钮,一个开启轮询,一个停止轮询,轮询的方法命名为myInterval,返回一个startstop方法用于开始和停止轮询

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <button id="start">开始</button>
    <button id="stop">停止</button>
  </body>
</html>
<script>
  const startBtn = document.getElementById('start')
  const stopBtn = document.getElementById('stop')
  // 轮询在这
  function myInterval() {
      return {
          start: () => {},
          stop: () => {}
      }
  }
  // 轮询管理器
  const intervalManager = myInterval(main)
  // 轮询的方法
  let count = 0
  function main() {
    count += 1
    console.log('执行:', count)
  }
  startBtn.addEventListener('click', intervalManager.start)
  stopBtn.addEventListener('click', intervalManager.stop)
</script>

基础版

function myInterval(callback, interval = 2000) {
    let timerId
    const loop = async () => {
      callback()
      return (timer = setTimeout(loop, interval))
    }
    return {
      start: () => {
        loop()
      },
      stop: () => {
        console.log('停止执行')
        clearTimeout(timerId)
      }
    }
  }

用js实现可终止的轮询请求

这个版本基本跟setInterval的elementary是什么意思功能一致了,只不过执elementanimation行需要手动调用start,停止要调用stop。

一个常见的场景是,当轮询执行n次后,停止轮询,下面改一下main方法,等count等于5的时候停止轮询

function main() {
    count += 1
    console.log('执行:', count)
    if (count == 5) {
      count = 0
      intervalManager.stop()
    }
}

用js实现可终止的轮询请求

发现当count等于5时,确实调用了stop方法,但是却没有停止轮询,当点击停止按钮的时候,又停止了轮询,这是什么情况呢?

看一下loophtml代码方法

const loop = async () => {
  callback()
  return (timerId = setTimeout(() => {
    loop()
  }, interval))
}

因为是在callback中执行selementary翻译top的,stop并没有阻止定时器中回调(loop)的执行,所以看起来是停止接口测试了,但是新的定时器还是开启了

进阶版

解决基础版无法自动停止的问题也很简单,加一个变量来检测,一旦执行了stop方法不返回定时器就可以了

// 可以自动停止的定时器
function myInterval(callback, interval = 2000) {
    let timer
    let isStop = false
    const stop = () => {
      console.log('停止')
      isStop = true
      clearTimeout(timer)
    }
    const start = () => {
      isStop = false
      loop()
    }
    const loop = async () => {
      callback()
      if (isStop) return
      return (timer = setTimeout(loop, interval))
    }
    return {
      start,
      stop
    }
  }

用js实现可终止的轮询请求

下面把main方法再改一改

function main() {
    const flag = parseInt(Math.random() * 2) === 1
    console.log('flag', flag)
    return flag ? Promise.resolve() : Promise.reject()
}

main方法这次返回的是Promise,我们来看看执行的情况

用js实现可终止的轮询请求

进阶版变量值还是不支持main接口英文中有Promise的情况,我们希望的是,当main中出现Promise rejecelementanimationt和错误的时候,中止轮询

最终版

asyncawait可以实现main方法中出现错误的时候中止轮询

function myInterval(callback, interval = 2000) {
    let timer
    let isStop = false
    const stop = () => {
      console.log('停止')
      isStop = true
      clearTimeout(timer)
    }
    const start = async () => {
      isStop = false
      await loop()
    }
    const loop = async () => {
      try {
        await callback(stop)
      } catch (err) {
        console.error('轮询出错:', err)
        throw new Error('轮询出错:', err)
      }
      if (isStop) return
      return (timer = setTimeout(loop, interval))
    }
    return {
      start,
      stop
    }
  }

用js实现可终止的轮询请求

可以看到最终版能满足我们的轮询要求了,遇到接口报错的时候可以终止请求,细心的您应该能发现这行代码 callback(stop)http://www.baidu.com,是的,main中现在多了一个回调方法,可以直接调用该方法停止轮询

main再次改回来

let count = 0
function main(stop) {
    count += 1
    console.log('count:', count)
    if (count == 5) {
      stop()
    }
}

用js实现可终止的轮询请求

可以看到,调用stop也是可以中止轮询的

html5在一个完美的js可终止轮询请求,interval就完成啦~