使用setInterval()进行简单的连续轮询

74

对于一个简单的 Web 应用程序,需要以一定的时间间隔刷新向用户展示的部分数据,仅使用 setInterval() 从端点获取 JSON 数据而不使用适当的轮询框架是否存在任何缺点?

举例来说,假设我每 5 秒钟刷新一次处理作业状态。


10
我会使用 setTimeout,并在前一个响应接收后总是调用它。这样可以避免可能出现的拥堵或函数堆积等问题。 - Felix Kling
我按照@FelixKling所说的方法做了,结果非常顺利。你也试试看! - Alfabravo
太棒了!@FelixKling,你能把那个作为答案发一下吗?我会接受的。 - Sologoub
5个回答

96

根据我的评论:

我会使用setTimeout [文档],并在每次收到上一响应后调用它。这样,如果请求/响应需要比您的间隔时间更长,就可以避免可能出现的拥塞或函数堆叠等问题。

代码如下:

function refresh() {
    // make Ajax call here, inside the callback call:
    setTimeout(refresh, 5000);
    // ...
}

// initial call, or just call refresh directly
setTimeout(refresh, 5000);

3
这里有一篇很好的分析,解释了为什么避免使用setInterval是个好主意:http://weblogs.asp.net/bleroy/archive/2009/05/14/setinterval-is-moderately-evil.aspx - Dave Ward
我们应该两次使用 setTimeout(refresh, 5000); 吗?我的意思是在初始调用之后再在被调用的函数中再次使用它? - David
@danielgi:这有什么意义呢?在调用refresh时,超时时间已经“完成”了。没有什么需要清除的。 - Felix Kling
2
这里会有更多的堆栈分配吗?因为这是一个嵌套在另一个嵌套在另一个嵌套中的轮询,递归函数太多了。 - Himanshu Shekhar
1
如果请求永远不完成,setTimeout可能会阻塞代码。 - france1
显示剩余3条评论

21

最近的浏览器可以使用Promises实现一个简单的非阻塞轮询函数:

var sleep = duration => new Promise(resolve => setTimeout(resolve, duration))
var poll = (promiseFn, duration) => promiseFn().then(
             sleep(duration).then(() => poll(promiseFn, duration)))

// Greet the World every second
poll(() => new Promise(() => console.log('Hello World!')), 1000)

2
好的。运行正常。如果我决定通过点击按钮停止执行,我应该如何做呢? - Marcel
@Marcel 谢谢。简短的回答是不可能的(https://dev59.com/il0b5IYBdhLWcg3wM-0c#29479435)。 - bschlueter
另一个使用 Promise 的解决方案:https://morioh.com/p/2e1c6c90f85a - thdoan

6
您可以这样做:
var i = 0, loop_length = 50, loop_speed = 100;

function loop(){
    i+= 1; 
    /* Here is your code. Balabala...*/
    if (i===loop_length) clearInterval(handler);
}

var handler = setInterval(loop, loop_speed);

1

只需修改@bschlueter的答案,是的,您可以通过调用cancelCallback()来取消此轮询功能。

let cancelCallback = () => {};

var sleep = (period) => {
  return new Promise((resolve) => {
    cancelCallback = () => {
      console.log("Canceling...");
      // send cancel message...
      return resolve('Canceled');
    }
    setTimeout(() => {
      resolve("tick");
    }, period)
  })
}

var poll = (promiseFn, period, timeout) => promiseFn().then(() => {
  let asleep = async(period) => {
    let respond = await sleep(period);
    // if you need to do something as soon as sleep finished
    console.log("sleep just finished, do something...");
    return respond;
  }


  // just check if cancelCallback is empty function, 
  // if yes, set a time out to run cancelCallback()
  if (cancelCallback.toString() === "() => {}") {
    console.log("set timout to run cancelCallback()")
    setTimeout(() => {
      cancelCallback()
    }, timeout);
  }

  asleep(period).then((respond) => {
    // check if sleep canceled, if not, continue to poll
    if (respond !== 'Canceled') {
      poll(promiseFn, period);
    } else {
      console.log(respond);
    }
  })

  // do something1...
  console.log("do something1...");

})


poll(() => new Promise((resolve) => {
  console.log('Hello World!');
  resolve(); //you need resolve to jump into .then()
}), 3000, 10000);

// do something2...
console.log("do something2....")


1
我知道这是一个古老的问题,但我偶然发现了它,并按照StackOverflow的方式想要改进它。你可以考虑类似于这里所描述的的解决方案,即长轮询。或者另一种解决方案是WebSockets(其中一个更好的实现是旨在在所有浏览器上工作的WebSockets)socket.io
第一个解决方案基本上概括为您发送单个AJAX请求并等待响应,然后再发送另一个请求,在响应被传递后,排队下一个查询。
同时,在后端,直到状态改变才返回响应。因此,在您的场景中,您将利用一个while循环,该循环将继续,直到状态改变,然后将更改后的状态返回到页面。我真的很喜欢这个解决方案。正如上面链接的答案所示,这就是Facebook所做的事情(或者至少过去曾经这样做)。
socket.io基本上是Websockets的jQuery,因此无论您的用户使用哪个浏览器,您都可以建立一个套接字连接,该连接可以将数据推送到页面(完全不需要轮询)。这更接近于Blackberry的即时通知,如果您想要即时性,则是最佳解决方案。

虽然socket.io可能很好,但它并不是WebSocket的唯一解决方案,只是众多解决方案之一。几乎所有的解决方案都有其自身的优点,你可以根据需要考虑不同的框架来解决问题。 - Uffe
粗心大意,已更新以反映提到socket.io的意图。 - th3byrdm4n

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接