使用Ajax实现异步任务
我们经常会遇见许多要运行很长时间的任务,如果还是按照常规的页面请求方式,就会产生卡顿,页面假死现象。 这时候我们第一个想到的就是将这种同步请求方式转换成异步请求,然而对于不需要关心返回结果的请求这个非常简单, 大部分情况是我们还得知道异步任务返回结果,然后调用回调函数来更新页面结果。
目前常见的三种方式是Ajax轮训、Ajax长连接(long polling)、WebSocket方式。 这里我只讲Ajax的两种方式,因为更好的WebSocket方式我已经单独写了一篇文章来介绍。
通过一个实际例子来演示下。
服务器实现
这里通过一个SpringMVC项目,实现服务器长时间任务部分:
1 |
|
服务器端在进行长连接的程序设计时,要注意以下几点:
- 服务器程序对轮询的可控性。由于轮询是用死循环的方式实现的,所以在算法上要保证程序对何时退出循环有完全的控制能力, 避免进入死循环而耗尽服务器资源。
- 合理选择”心跳”频率。长连接必须由客户端不停地进行请求来维持,所以在客户端和服务器间保持正常的”心跳”至为关键, 参数POLLING_LIFE应小于WEB服务器的超时时间,一般建议在10~20秒左右。也就是ajax请求中timeout: 5000参数
- 网络因素的影响。在实际应用时,从服务器做出应答,到下一次循环的建立,是有时间延迟的,延迟时间的长短受网络传输等多种因素影响, 在这段时间内,长连接处于暂时断开的空档,如果恰好有数据在这段时间内发生变动,服务器是无法立即进行推送的, 所以,在算法设计上要注意解决由于延迟可能造成的数据丢失问题。
- 服务器的性能。在长连接应用中,服务器与每个客户端实例都保持一个持久的连接,这将消耗大量服务器资源, 特别是在一些大型应用系统中更是如此,大量并发的长连接有可能导致新的请求被阻塞甚至系统崩溃, 所以,在进行程序设计时应特别注意算法的优化和改进,必要时还需要考虑服务器的负载均衡和集群技术。
轮训方式
setTimeout和setInterval
它们的定义:
- setTimeout 指定延迟后调用函数
- setInterval 以指定周期调用函数
对于setInterval
而言,如果第一个函数的执行时间特别长,在执行的过程中本应触发了许多个func怎么办,
那么所有这些应该触发的函数都会进入队列吗?
不,只要发现队列中有一个被执行的函数存在,那么其他的统统忽略。如下图,在第300毫秒和400毫秒处的回调都被抛弃, 一旦第一个函数执行完后,接着执行队列中的第二个,即使这个函数已经”过时”很久了。
还有一点,虽然你在setInterval的里指定的周期是100毫秒,但它并不能保证两个函数之间调用的间隔一定是一百毫秒。 在上面的情况中,如果队列中的第二个函数时在第450毫秒处结束的话,在第500毫秒时,它会继续执行下一轮func, 也就是说这之间的间隔只有50毫秒,而非周期100毫秒
那如果我想保证每次执行的间隔应该怎么办?用setTimeout,比如下面的代码:
1 | var i = 1 |
上面的函数每2秒钟递归调用自己一次,你可以在某一次alert的时候等待任意长的时间(不按”确定”按钮), 接下来无论你什么时候点击”确定”, 下一次执行一定离这次确定相差2秒钟的
两者的清除:
1 | t1 = setTimeout(xx(), 1000); |
使用setInterval轮训
客户端实现的就是用一种普通轮询的结果,比较简单。利用setInterval
不间断的刷新来获取服务器的资源,
这种方式的优点就是简单、及时。
缺点是链接多数是无效重复的;响应的结果没有顺序(因为是异步请求,当发送的请求没有返回结果的时候,后面的请求又被发送。 而此时如果后面的请求比前面的请求要先返回结果,那么当前面的请求返回结果数据时已经是过时无效的数据了); 请求多,难于维护、浪费服务器和网络资源。
看看实际的轮训例子:
1 | var count1 = 0; |
运行效果:
可以发现,后面请求不会等待前面请求完,也就是也许会同时出现多个HTTP连接服务器,造成服务器资源大量浪费。
长连接
这种是Ajax最常见的请求方式,我们通常使用ajax异步提交请求时候就是用的这种方式:
1 | // Ajax long polling 方式 |
主要优点就是和服务器始终保持一个连接。如果当前连接请求成功后,将更新数据并且继续创建一个新的连接和服务器保持联系。 如果连接超时或发生异常,这个时候程序也会创建一个新连接继续请求。这样就大大节省了服务器和网络资源,提高了程序的性能, 从而也保证了程序的顺序。
运行效果图:
可以发现,要么前面的运行成功,那么超时,每个时候最多有一个HTTP连接。