直接进入主题,本篇文章有点长,包括从设计阶段,到摸索阶段,再到实现阶段,最后全面覆盖测试阶段(包括数据搜集清洗),还有与主流前端通信框架进行对比PK阶段。

首先介绍一下一些概念:

  1. 浏览器的并发能力:浏览器设计当初就定义了浏览器打开页面,同时发送http请求的瞬时数量。这样设计有很多原因,同时保护浏览器和服务器。具体可以谷歌或者百度关键字:浏览器并发。

  2. 浏览器针对服务器域名请求的并发限制数量:

    

  3. 请求池:类似于数据库连接池一样,对数据库请求连接进行分配管理等等

  4. 复用请求:对于生命周期已经结束的请求不进行销毁,重复利用,减少重新向浏览器申请资源,减少通用库设计中的通用检查。

引入新功能:连接池

当初为啥要做这个功能:

  上次迭代最后留了一个彩蛋,我在一次测试中,发送完成一个请求之后,将这个请求存到一个变量中,然后又尝试重新打开,发现竟然可以发送出去。好奇心害死猫,我在浏览器中用console.time去计算执行这段js代码的时间,发现重复使用的请求使用时间大概是前者的一半,甚至更少。既然,他可以这么优秀,那我为啥不能欲罢不能呢。所以,这一步下去就是一波三折...

摸索思路:

按着当时的设计思路一步一步的来确认

   1. 首先确认了一个请求发送完了之后,可以再次打开然后发送。这样就保证了方向是对的,可以继续优化下去

   2. 其次,对于一个域名,浏览器的同时发送请求的数目就如上面图片上描述的,主流基本都是6个,所以请求池将基于浏览器最大并发做连接的配置,不过因为浏览器差异,会将连接池的连接数量做配置进行配置

   3. 将这个请求池塞满配置的请求将是第一任务,对于XMLHttpRequest这个对象,主流的想法都是做一个拷贝,当前浅拷贝是不行的,那就做了一次深度拷贝测试,塞满请求池。不过,该方案夭折了。

   4. 因为请求对象的原型是XMLHttpRequest,这个对象实现了浏览器的接口才能通过浏览器API发送http请求,但是深度拷贝的对象是Object,object不实现浏览器的接口,所以夭折了,无法直接拷贝,难道我要类似于发送6次空请求之后去搜集请求池连接吗?这个是绝对不可能饶恕的行为,所以做了下一步的摸索

   5. 如果XMLHttpRequest对象实现了浏览器的接口,那么我们将深度拷贝的对象的原型的指向重新指向到XMLHttpRequest。这样是不是可以发送请求了。然后测试了,阿西吧,非法请求,而且对于XMLHttpRequest对象中的response、responseText等熟悉还是只读了,重新拷贝赋值浏览器报错,非法,全都是非法。

   6. 气馁了很久,把大脑放空换个思路去解决这个问题。参考了其他类似请求池的设计思路,要么一开始就做初始化,将池子中的连接一次性生产出来,或者每次建立的连接消费完毕之后再归入池子中,等待连接满了之后才启用连接池。

   7. 考虑到前端每个页面,或者对于现在的每个组件中能发送请求的数量并不是很大,可能一个路由下就发送了4-5个请求就解决一个小业务需求,根本达不到启动请求池的基本配置。其次,通过浏览器的performance监控页面性能,发现初始化的时间就在几毫秒之间,相对于后期使用加速,这个时间的付出,对于后期的使用是很值得的预算。所以,选择了第一种方式,一次性初始化请求池,等待调用。下面缕清思维,做了更完整和全面的设计。

请求池设计方案:

左边 -- 生成请求池部分

  1. 加载ajax-js库的时候,通过判断请求池开关是否打开,打开的话就走初始化这一套流程。

  2. 创建基于全局配置的空请求-->copy所需参数-->创建空XMLHttpRequest对象-->合并copy参数到空对象上-->读取配置数量生成请求池

  3. 因为请求池是基于全局配置生成的请求,所以如果全局配置变动,将触发请求池的reload,重新生成。

中间 -- 通过请求池发送请求的生命周期

  1. 请求进来,首先判断开关是否打开,打开的话就先判断请求池中请求的数量是否还有。

  2. 对比参数,是否有额外预设参数的变更(除url、data、successEvent、eroorEvent外),如果有将在请求发送前进行设置。(例如:request的Header将在每次开启的时候全部重新置空)

  3. 在请求的生命周期结束之后,然后归还请求池。注意:请求的生命周期结束有很多状况,发送错误url、timeout超时等在浏览器层面就失败的,以及请求发送成功时候,非200、304等错误,比如4XX或者5XX系列错误的生命周期结束

  4. 最后判断是否有排队的请求,有的话取出再来,没有直接返回池子

右边 -- 请求排队系统

  1. 对于请求如果超出请求池数量,请求池肯定无法承载,就算能承载,其实浏览器也是将这些超出并发的请求进行排队,等待一波结束之后再发送。所以有个排队的系统很重要的啦,来管理请求。

  2. 对于超出容量请求,首先排队。然后,在请求生命结束之后,将会去检查排队系统是否还有排队请求。其次,如果有就进行取出,再走请求的生命周期,如果每页,那就没请求啦

代码片段展示:

1. 加载类库时判断开关,初始化代码

   var outputObj = function () {
//虽然在IE6、7上可以支持,但是最好升级你的浏览器,毕竟xp已经淘汰,面向未来吧,骚年,和我一起努力吧!!
if (tool.getIEVersion() < 7) {
//实在不想说:升级你的浏览器吧
throw new Error("Sorry,please update your browser.(IE8+)");
} // 是否开启连接池
if (initParam.pool.isOpen) {
tool.createPool()
} return tempObj;
};

2. 创建方法

     // 创建请求池中链接
createPool: function () {
// IE 系列不支持发送请求传'',所以默认/
tempObj.common({url: '/'}, true)
tool.deepCloneXhr(selfData.xhr, initParam.pool.requestNumber)
},

3. 拷贝通用参数存入请求池

     // 拷贝xhr参数
deepCloneXhr: function (data, requestNum) {
var mapping = {
currentUrl: true,
onerror: true,
onload: true,
onreadystatechange: true,
ontimeout: true,
timeout: true, // IE系列只有open连接之后才支持覆盖
withCredentials: true,
xhr_ie8: true
}
var temp = {} for (var key in data) {
if (mapping[key]) {
if (!isNaN(tool.getIEVersion()) && key !== 'timeout') {
temp[key] = data[key]
} else {
var newKey = '_' + key
temp[newKey] = data[key]
}
}
} for (var i = 0; i < requestNum; i++) {
var nullRequest = tool.createXhrObject()
tool.MergeObject(nullRequest, temp)
selfData.requestPool.push(nullRequest)
}
},

4. 使用加速,判断是否开启(只举例post)

     //异步post请求
post: function (url, data, successEvent, errorEvent, timeoutEvent) {
var ajaxParam = {
type: "post",
url: url,
data: data,
contentType: '',
successEvent: successEvent,
errorEvent: errorEvent,
timeoutEvent: timeoutEvent
}; if (initParam.pool.isOpen) {
tool.useRequestPool(ajaxParam)
} else {
tempObj.common(ajaxParam);
}
},

5. 使用请求池链接,设置基本配置(url,datda,successEvent)

     // 请求池申请请求使用
useRequestPool: function (param) {
// 判断请求池中是否有可用请求
if (selfData.requestPool.length !== 0) {
var temp = selfData.requestPool.shift(), sendData = '', tempHeader = {}
// 赋值操作,将数据捆绑到原型上
temp.callback_success = param.successEvent
temp.callback_error = param.errorEvent
temp.callback_timeout = param.timeoutEvent
temp.data = param.data // 处理参数
switch (param.contentType) {
case '':
tool.each(tool.MergeObject(param.data, initParam.publicData), function (item, index) {
sendData += (index + "=" + item + "&")
});
sendData = sendData.slice(0, -1);
break
case 'json':
sendData = JSON.stringify(tool.MergeObject(param.data, initParam.publicData))
break
case 'form':
if (!tool.isEmptyObject(initParam.publicData)) {
tool.each(initParam.publicData, function (item, index) {
param.data.append(index, item)
})
}
sendData = param.data
break
} //判断请求类型
if (param.type === 'get') {
temp.open(param.type, tool.checkRealUrl(param.url, temp) + (sendData === '' ? '' : ('?' + sendData)))
} else {
temp.open(param.type, tool.checkRealUrl(param.url, temp))
} param.responseType ? (temp.responseType = param.responseType) : null if (!isNaN(tool.getIEVersion())) {
temp.timeout = temp._timeout
} switch (param.contentType) {
case '':
tempHeader['Content-Type'] = 'application/x-www-form-urlencoded'
break
case 'json':
tempHeader['Content-Type'] = 'application/json'
break
} //设置http协议的头部
tool.each(tool.MergeObject(tempHeader, initParam.requestHeader), function (item, index) {
temp.setRequestHeader(index, item)
}); //发送请求
temp.send(param.type === 'get' ? '' : sendData);
} else {
// 没有请求,加载到待发送队列中
selfData.queuePool.push(param)
}
},

6. 在默认周期中回收链接(onreadystatechange和onload)

       //xmlhttprequest每次变化一个状态所监控的事件(可拓展)
xhr.onreadystatechange = function () {
switch (this.readyState) {
case 1://打开
//do something
break;
case 2://获取header
//do something
break;
case 3://请求
//do something
break;
case 4://完成
//在ie8下面,无xhr的onload事件,只能放在此处处理回调结果
if (this.xhr_ie8) {
if (this.status === 200 || this.status === 304) {
if (this.responseType == "json") {
this.callback_success ?
this.callback_success(ajaxSetting.transformResponse(JSON.parse(this.responseText))) :
ajaxSetting.successEvent(ajaxSetting.transformResponse(JSON.parse(this.responseText)))
} else {
this.callback_success ?
this.callback_success(ajaxSetting.transformResponse(this.responseText)) :
ajaxSetting.successEvent(ajaxSetting.transformResponse(this.responseText))
}
} else {
// 请求错误搜集
tool.uploadAjaxError({
type: 'request',
errInfo: JSON.stringify(this.data ? this.data : ajaxSetting.data),
errUrl: this.currentUrl,
errLine: this.status,
Browser: navigator.userAgent
})
}
// 针对IE8 请求池处理
if (ajaxSetting.pool.isOpen) {
tool.responseOver(this)
}
} else {
if (this.status === 0) {
// 发送不存在请求,将不会走onload,直接这里就挂了,请求归还请求池
if (ajaxSetting.pool.isOpen) {
tool.responseOver(this)
}
}
}
break;
}
;
};
       //onload事件(IE8下没有该事件)
xhr.onload = function (e) {
if (this.readyState === 4 && (this.status == 200 || this.status == 304)) {
/*
* ie浏览器全系列不支持responseType='json'和response取值,所以在ie下使用JSON.parse进行转换
* */
if (!isNaN(tool.getIEVersion())) {
if (this.responseType === 'json') {
this.callback_success ?
this.callback_success(ajaxSetting.transformResponse(JSON.parse(this.responseText))) :
ajaxSetting.successEvent(ajaxSetting.transformResponse(JSON.parse(this.responseText)));
} else {
this.callback_success ?
this.callback_success(ajaxSetting.transformResponse(this.responseText)) :
ajaxSetting.successEvent(ajaxSetting.transformResponse(this.responseText));
}
} else {
this.callback_success ?
this.callback_success(ajaxSetting.transformResponse(this.response)) :
ajaxSetting.successEvent(ajaxSetting.transformResponse(this.response));
}
} else {
/*
* 这边为了兼容IE8、9的问题,以及请求完成而造成的其他错误,比如404等
* 如果跨域请求在IE8、9下跨域失败不走onerror方法
* 其他支持了Level 2 的版本 直接走onerror
* */
this.callback_error ?
this.callback_error(e.currentTarget.status, e.currentTarget.statusText) :
ajaxSetting.errorEvent(e.currentTarget.status, e.currentTarget.statusText); // 请求错误搜集
tool.uploadAjaxError({
type: 'request',
errInfo: JSON.stringify(this.data ? this.data : ajaxSetting.data),
errUrl: this.currentUrl,
errLine: this.status,
Browser: navigator.userAgent
})
} // 生命周期结束之后返回数据池,不绑定状态(是否为成功或失败状态)
if (ajaxSetting.pool.isOpen) {
tool.responseOver(this)
}
};

7. 回收方法

     // 请求周期结束操作
responseOver: function (xhr) {
selfData.requestPool.push(xhr)
if (selfData.queuePool.length > 0) {
var tempData = selfData.queuePool.shift()
tool.useRequestPool(tempData)
}
}

以下为测试结果展示(测试覆盖面:针对主流浏览器测试自身不开请求池、开启请求池、与主流框架axios、jquery的ajax对比,对比精度连续发送10、100、1000、5000次请求):

chrome(测试单位:微秒):

firefox(测试单位:毫秒)  PS:在5000的请求测试下,除了开启请求池可以正常进行,其他方式全都浏览器崩溃

safari(测试单位:微秒):

opera(测试单位:微秒)

edge(测试单位:毫秒)

IE11(测试单位:毫秒)

IE9(测试单位:毫秒)

IE8(测试单位:毫秒)   PS:在IE8下面没有找到axios ie8下运行方案,jquery就不做测试,就做自身对比测试

首先陈述几个问题:

  1. 测试单位取值

    使用浏览器的console.time方法去取值,在不同浏览器获得的参数不一样

  2. 为什么不同意取值

    因为ajax-js在开启请求池的用时中达到了0.00X等级,也就是微秒的级别,在折线图中无法展示,所以增大单位,好看出趋势

从所有清洗数据获得的统计图中总结以下:

  1. jquery.ajax速度和性能最慢

  2. ajax-js 开启请求池状况下,性能最好,最快

  3. ajax-js类库不开启请求池,相对来说,比jquery中的ajax性能高,但是,不得不承认,axios比ajax-js优秀。

  4. ajax-js类库开启请求池的状况下,针对于自身,性能提高至少一倍以上,比axios还快,快很多

数据搜集在:https://github.com/GerryIsWarrior/ajax/tree/master/ajax-dataCenter/collect

清洗完数据形成的报表:https://github.com/GerryIsWarrior/ajax/tree/master/ajax-dataCenter/collect

数据清洗的工具:https://github.com/GerryIsWarrior/ajax/tree/master/ajax-dataCenter/tool

清洗完成形成的数据报表可以直接打开,可以更清晰的看数据

如需测试,可以去github下载该项目,在ajax-interface文件下,写了一个express的node服务器,可以 npm i 初始化之后,然后 npm run start 启动。然后在ajax-testing目录下有个html文件,提供了测试案例和一些简单demo。

此次对外暴露的方法都开启了请求池加速(开启全局加速配置),除了common方法,预留一个未加速通用方法,防止有特殊需求。而且,本次请求池只是针对一个域名的加速,二期,将会增加针对不同域名加速的请求池。

github地址:https://github.com/GerryIsWarrior/ajax   对你有帮助或启发,点个小星星,支持继续研究下去

这次的迭代一波三折,中间有很多次走到一半就持续不下去了,但是因为刚开始测试的基础方向是对的,所以不想放弃,然后都是理顺思路一步一步的去走,看自己到底错在什么地方,这个方向为什么不能走。还有为了这次测试,做了大量的数据搜集,从mac系统和window不停切换,为主流浏览器做兼容测试和主流框架对比测试数据搜集。搜集完成之后,耐心的做数据清洗,将混杂的数据,进行分类归纳,然后找对应报表,最后将数据加载到报表上,做更直观的展示。

过程是艰难的,结果是值得兴奋的。正视自己的不足,axios是一个优秀的框架,速度和性能绝对是很好的。在没有做请求池优化之前,自己写的库达不到那个程度,不过在开启了请求池之后,在性能和速度方面已经超过了axios,jquery就不谈了。在研究前端通信的过程中收获了很多很多,从基础到设计,从底层到优化,没有什么是解决不了的问题。认清自己,做更好的自己,没有什么是不可能的。

从0-1是艰难的,从99-100更是难上加难。但是,只要去做,终是离那个方向更近一点,前端共勉!!

下一步迭代:更完备的文档和更完备的测试

前端通信:ajax设计方案(八)--- 设计请求池,复用请求,让前端通信快、更快、再快一点的更多相关文章

  1. 前端通信:ajax设计方案(一)---集成核心请求

    报告,我要说话!xp被历史淘汰了,IE6 say goodbye了,太TM开心了,从此不要兼容IE6了,哈哈哈哈哈哈 报告,我要说话!IE这sb为啥不早点被杀掉呢,找工作听说要兼容IE,立马软了,唉唉 ...

  2. 框架基础:ajax设计方案(一)---集成核心请求

    报告,我要说话!xp被历史淘汰了,IE6 say goodbye了,太TM开心了,从此不要兼容IE6了,哈哈哈哈哈哈 报告,我要说话!IE这sb为啥不早点被杀掉呢,找工作听说要兼容IE,立马软了,唉唉 ...

  3. 前端通信:ajax设计方案(九)--- 完善文档

    ajax-js 1.9.1 文档 目录 * common(options, isCreatePoll) * config(options) * get(url, data, successEvent, ...

  4. 前端通信:ajax设计方案(七)--- 增加请求错误监控、前端负载均衡以、请求宕机切换以及迭代问题修复

    距离上个迭代过了很长时间,中间经历了很多事情,也在每个空余时间构思了这个迭代的东西以及下个迭代要做的东西.时间周期稍微长了,望见谅. 而且,至今这个开源库的start也已经到了165个了,会支持关注和 ...

  5. 前端通信:ajax设计方案(三)--- 集成ajax上传技术

    在此之前让我感慨一下现在的前端开发的氛围.我遇到好多人,给我的观念都是,这个东西这个框架有了,那个东西那个框架做了,前端嘛,学几个框架,这个拼凑一下那个拼凑一下就好了.其实我想问,东西都框架做了,那你 ...

  6. 前端通信:ajax设计方案(四)--- 集成ajax上传技术 大文件/超大文件前端切割上传,后端进行重组

    马上要过年了,哎,回家的心情也特别的激烈.有钱没钱,回家过年,家永远是舔舐伤口最好的地方.新的一年继续加油努力. 上次做了前端的ajax的上传文件技术,支持单文件,多文件上传,并对文件的格式和大小进行 ...

  7. 前端通信:ajax设计方案(五)--- 集成promise规范,更优雅的书写代码(改迭代已作废,移步迭代10)

    该迭代已作废,最新的请移步这里:https://www.cnblogs.com/GerryOfZhong/p/10726306.html 距离上一篇博客书写,又过去了大概几个月了,这段时间暂时离开了这 ...

  8. 框架基础:ajax设计方案(三)--- 集成ajax上传技术 大文件/超大文件前端切割上传,后端进行重组

    马上要过年了,哎,回家的心情也特别的激烈.有钱没钱,回家过年,家永远是舔舐伤口最好的地方.新的一年继续加油努力. 上次做了前端的ajax的上传文件技术,支持单文件,多文件上传,并对文件的格式和大小进行 ...

  9. 前端ajax中运用post请求和get请求之于session验证

    首先我们来看下ajax两种请求的区别: Ajax中POST和GET的区别Get和Post都是向服务器发送的一种请求,只是发送机制不同. 1. GET请求会将参数跟在URL后进行传递,而POST请求则是 ...

随机推荐

  1. Visual C++实现局域网IP多播

    //////////////////////////////////////////////////////////////////////////////////////////////////// ...

  2. 1085. Perfect Sequence

    Given a sequence of positive integers and another positive integer p. The sequence is said to be a “ ...

  3. (数学)Knight's Trip -- hdu -- 3766

    http://acm.hdu.edu.cn/showproblem.php?pid=3766 Knight's Trip Time Limit: 2000/1000 MS (Java/Others)  ...

  4. Curl工具的使用

      Curl命令可以通过命令行的方式,执行Http请求.在Elasticsearch中有使用的场景,因此这里研究下如何在windows下执行curl命令. 工具下载 在官网处下载工具包:http:// ...

  5. HDU2444 The Accomodation of Students

    The Accomodation of Students Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K ( ...

  6. 第71讲:Scala界面Panel、Layout实战详解

    今天学习了王家林老师scala讲座的第71讲,scala界面编程panel实战.让我们一起来看一下. 信息来源于 DT大数据梦工厂微信公众账号:DT_Spark 关注微信账号,获取更多关于王家林老师的 ...

  7. 深入探讨 Java 类加载器(转)

    原帖地址:https://www.ibm.com/developerworks/cn/java/j-lo-classloader/ 类加载器是 Java 语言的一个创新,也是 Java 语言流行的重要 ...

  8. Android-Retrofit-2.0-Post与Get-请求有道词典翻译

    Retrofit-2.0版本后,内置已经集成了OKHttp,在使用Retrofit的时候 看似是Retrofit去网络请求的 实际上Retrofit只是封装,所以不要以为Retrofit是网络请求框架 ...

  9. 敏捷Scrum框架最全总结! [转载]

    [原文链接] 2016-05-03 Sting 敏捷开发作为目前流行的开发方法,为快速迭代提供了足够的理论支持,但敏捷开发方式不应该成为忽略文档和需求分析的过程,注意每个sprint的引入,任务燃烧, ...

  10. .net core 与ELK(5)安装logstash

    1.下载https://www.elastic.co/downloads/logstash到/usr/local/src wget https://download.elastic.co/logsta ...