一, 背景
开发 web 平台时, 经常会需要定时向服务器轮询获取数据状态, 并且通常不仅只开一个轮询, 而是根据业务需要会产生数个轮询. 这种情况下, 性能低下的 Ajax 长轮询已经不能满足需求, 频繁的访问还会造成线程阻塞. 最优的解决方案当然是用 Websocket, 采用服务器推送的方式来减少频繁开关连接造成的开销. 但是 Websocket 对于我来说还只是个新事物, 在未完成论证的情况下不能直接开发完就上, 因此只好采用过渡方案, 使用队列的方式, 暂时优化多 AJax 长轮询的情况下造成的线程阻塞问题.
我所用的 Web 平台框架是国产开源的 DWZ 框架. 该框架不使用经典的 iframe 模式, 所有的视图, 数据访问都是通过 Ajax 获取后在前台进行加载渲染, 页面迁移跳转极少, 因此本质上来说基于 DWZ 框架的网页都是 Single Page 页面. 在这种情况下, 除了长轮询外, 还会根据用户的操作产生其它 Ajax 链接. 这就要求在优化的同时, 还要保证用户操作的优先度. 毕竟长轮询只是后台默认执行的操作, 对用户的体验影响不大; 但用户的操作因为长轮询造成延迟的话, 用户体验就十分糟糕.
此外, 我还发现处理这些 Ajax 轮询所用的 Controller 是 MVC 默认的, 然而这些 Controller 不支持异步处理请求操作, 在多个请求访问时, 新请求必须等待旧请求完成后才能继续下去.
综上所述, 优化 Ajax 轮询造成的线程阻塞问题的过渡方案中, 有以下两点要求:
1. 使用 Ajax 队列的方式, 不推倒现有的技术方案, 在原有的基础上快速修改.
2. 在 Ajax 队列优化过程中, 必须保证用户操作的优先度, 保证用户操作的及时响应.
3. 替换原有只支持同步 Action 的 Controller, 使用可支持异常 Action 的 Controller.
二, 前台代码解析
总体思路是:
1. 重写 jquery 既有的 ajax 方法, 将所有调用该方法的 ajax 全部注册到自定义的 ajax 程序池中.
2. 自定义 ajax 程序池分全局和非全局两类, 长轮询发起的 ajax 为非全局, 用户发起的 ajax 为全局.
3. 排队执行两个程序池中的请求, 一个请求完成后才继续执行下一个, 而非异步将所有 ajax 同时发起请求.
4. 全局 ajax 的优先度高, 如果当前正在执行非全局 ajax 且有未发起的全局 ajax, 则停止正在执行的非全局 ajax, 优先发送全局 ajax.
5. 非全局 ajax 只有在全局 ajax 全部完毕的情况下才会发送请求.
- // 所有 ajax 请求都注册到 DNE.LoadingAjax 的 ajax 程序池中, 排队发起请求, ajax 结束时删除.
- DNE.LoadingAjax = {
- jqAjax: $.ajax,
- requests: {}, // ajax 对象集合
- globalAjaxPool: [], // 全局 ajax 程序池
- unglobalAjaxPool: [], // 非全局 ajax 程序池
- interval: null, // ajax 循环定时器
- runningType: null, // 正在运行的 Ajax 类型 1: 全局 2: 非全局
- runningId: null,// 正在运行的 AjaxId
- // 注册 Ajax 到程序池中
- PushAjaxPool: function (request, options) {
- var urlComplete = request.complete;
- var requests = this.requests;
- var id = (request.tabId) ? request.tabId : request.url;
- // 请求结束时, 删除 ajax 对象
- request.complete = this.deleteAjax(urlComplete, id);
- // 将请求放到 ajax 程序池中
- var requestObj = {
- id: id,
- request: request,
- options: options
- };
- // 如果是获取 json 数据的请求, 则放入程序池中, 如果是获取 Js 或图片等资源的请求, 则直接执行
- if (requestObj.request.dataType == "json") {
- if (request.global) {
- // 如果是全局 ajax
- this.globalAjaxPool.push(requestObj);
- } else {
- // 如果不是全局 ajax
- this.unglobalAjaxPool.push(requestObj);
- }
- } else {
- var loadingAjax = DNE.LoadingAjax;
- loadingAjax.runAjax(requestObj);
- }
- if (!this.interval) {
- this.interval = window.setInterval(function () {
- var loadingAjax = DNE.LoadingAjax;
- // 如果当前有全局 Ajax 未运行, 则停止正在运行的非全局 Ajax
- if (loadingAjax.runningType != 1 && loadingAjax.globalAjaxPool.length> 0) {
- if (loadingAjax.runningType == 2 && loadingAjax.runningId) {
- loadingAjax.ajaxAbort(id);
- }
- // 运行最开头的全局 Ajax
- var reqObj = loadingAjax.globalAjaxPool.shift();
- loadingAjax.runAjax(reqObj);
- } else {
- // 如果当前没有正在执行的 Ajax, 并且非全局 Ajax 程序池中有对象
- if (loadingAjax.runningType == null && loadingAjax.unglobalAjaxPool.length> 0) {
- // 运行最开头的非全局 Ajax
- var reqObj = loadingAjax.unglobalAjaxPool.shift();
- loadingAjax.runAjax(reqObj);
- }
- }
- }, 100);
- }
- },
- // 删除 Ajax
- deleteAjax: function (urlComplete, id) {
- if (urlComplete && typeof (urlComplete) == "function") {
- urlComplete();
- }
- var loadingAjax = DNE.LoadingAjax;
- if (loadingAjax.requests[id]) {
- delete loadingAjax.requests[id];
- }
- // 如果程序池中已无请求, 则清空 ajax 循环定时器
- if (loadingAjax.globalAjaxPool.length <= 0 && loadingAjax.unglobalAjaxPool.length <= 0) {
- loadingAjax.interval = null;
- }
- // 如果当前请求结束, 则重置正在运行的 Ajax 类型及 AjaxId
- loadingAjax.runningType = null;
- loadingAjax.runningId = null;
- },
- // 执行 Ajax
- runAjax: function (reqObj) {
- var jqXHR = this.jqAjax(reqObj.request, reqObj.options);
- this.requests[reqObj.id] = jqXHR;
- },
- // 停止 Ajax
- ajaxAbort: function (id) {
- var jqXHR = this.requests[id];
- if (jqXHR) {
- jqXHR.abort();
- delete this.requests[id];
- }
- }
- };
- $(function () {
- $.extend({
- ajax: function (url, options) {
- // 所有 ajax 都注册到 ajax 程序池中
- DNE.LoadingAjax.PushAjaxPool(url, options);
- }
- });
- });
三, 后台代码解析 (待续)
来源: https://www.cnblogs.com/nonkicat/p/5105033.html