js轮询请求
黑化程序员 人气:30最近遇到了一个需求,需要每隔5s请求一个接口获取接口返回的结果,返回成功后停止请求,接口的返回的值有下面几种情况:
// 成功 { "code": 0, "msg": '成功' } // 查询中 { "code": -1, "msg": '结果查询中' } // 失败 { "code": 1, "msg": '返回结果失败' }
code
是接口的状态码,为0
的时候表示接口返回的成功,这时就不需要再请求接口。
实现这种需求首先想到的就是用轮询去做了。
什么是轮询请求?
通俗地说,轮询请求就是间隔相同的时间(如5s)后不断地向服务端发起同一个接口的请求,当然不能无限次去请求,所以轮询必须要有个停止轮询的机制
轮询的要点
1. 按照需要选择是否立即发起请求再进入轮询
2. 上一次的请求响应后到了指定的时间间隔后再继续执行下一次请求
3. 当轮询的请求发生报错的时候应该停止轮询
4. 被停止的轮询可以根据需要再次发起轮询
setInterval的问题
因为是不断请求,所以首先能想到的就是用setInterval
去实现,但是setInterval
做轮询的话,会有以下严重的问题
1. 即使调用的代码报错了,setInterval会持续的调用
2. setInterval不会等到上一次的请求响应后再执行,只要到了指定的时间间隔就会执行,哪怕有报错也会执行
3. setInterval定时不准确,这个跟事件循环有关,这里不展开啦~
实现轮询
实现轮询,只要按照轮询的要点去实现一个setInterval方法就可以了,下面用setTimeout
一步步去实现这个方法
准备工作
首先准备一个html模板,这个模板包含两个按钮,一个开启轮询,一个停止轮询,轮询的方法命名为myInterval
,返回一个start
和stop
方法用于开始和停止轮询
<!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) } } }
这个版本基本跟setInterval的功能一致了,只不过执行需要手动调用start,停止要调用stop。
一个常见的场景是,当轮询执行n次后,停止轮询,下面改一下main
方法,等count
等于5的时候停止轮询
function main() { count += 1 console.log('执行:', count) if (count == 5) { count = 0 intervalManager.stop() } }
发现当count
等于5时,确实调用了stop
方法,但是却没有停止轮询,当点击停止按钮的时候,又停止了轮询,这是什么情况呢?
看一下loop
方法
const loop = async () => { callback() return (timerId = setTimeout(() => { loop() }, interval)) }
因为是在callback
中执行stop的,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 } }
下面把main
方法再改一改
function main() { const flag = parseInt(Math.random() * 2) === 1 console.log('flag', flag) return flag ? Promise.resolve() : Promise.reject() }
main
方法这次返回的是Promise,我们来看看执行的情况
进阶版还是不支持main
中有Promise的情况,我们希望的是,当main
中出现Promise reject和错误的时候,中止轮询
最终版
用async
和 await
可以实现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 } }
可以看到最终版能满足我们的轮询要求了,遇到接口报错的时候可以终止请求,细心的您应该能发现这行代码 callback(stop)
,是的,main
中现在多了一个回调方法,可以直接调用该方法停止轮询
把main
再次改回来
let count = 0 function main(stop) { count += 1 console.log('count:', count) if (count == 5) { stop() } }
可以看到,调用stop
也是可以中止轮询的
现在一个完美的js可终止轮询请求,interval就完成啦~
加载全部内容