ajax设计方案
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
ajax-api
ajax-dataCenter
ajax-dev
ajax-interface
ajax-npm
ajax-testing
.gitignore
LICENSE
README.md

README.md

原生js的ajax设计方案(现已支持npm安装)

npm安装,文档见底部

npm i ajax-js  /  yarn add ajax-js

整理ajax设计方案原因如下

  1. 从资源合理利用的角度以及网站优化角度去想,每次为了那几个功能,去引用一个框架,不划算
  2. 拜读了w3c的ajax的设计方案,包括level1和level2的规范,有种豁然开朗的感觉
  3. 有朋友遇到ajax的跨域方案,各种纠结在心里,导致内心不能舒畅
  4. 自己的框架底层也要需要用到ajax的基础功能,(get post请求,对于level2的上传暂时没用到)
  5. 最关键的也是之前对这块概念十分模糊,所以开始整理ajax这块的设计方案

一些概念

  • 浏览器的同源策略:浏览器最基本的安全功能,同源是指,域名,协议,端口相同(所以我写的接口部署端口分别为1122和2211即不是同源,属于跨域)
  • ajax:是一种技术方案,依赖的是CSS/HTML/Javascript,最核心依赖是浏览器提供的XMLHttpRequest对象,这个对象使得浏览器可以发出HTTP请求与接收HTTP响应。
  • nginx:是一个高性能的HTTP和反向代理服务器
  • IIS:微软开发的的服务器,window系统自带
  • XMLHttpRequest 兼容性如下:
  • XMLHttpRequest Level 1主要存在以下缺点:
    1. 受同源策略的限制,不能发送跨域请求;
    2. 不能发送二进制文件(如图片、视频、音频等),只能发送纯文本数据;
    3. 发送和获取数据的过程中,无法实时获取进度信息,只能判断是否完成;
  • XMLHttpRequest Level 2中新增了以下功能:
    1. 可以发送跨域请求,在服务端允许的情况下;
    2. 支持发送和接收二进制数据;
    3. 新增formData对象,支持发送表单数据;
    4. 发送和获取数据时,可以获取进度信息;
    5. 可以设置请求的超时时间;

开始准备

  • 纯前端代码
  • nginx反向代理服务器(前后端分离用)
  • 后台2套接口(端口:1122,端口:2211) PS:一份必须支持跨域请求
  • IIS服务器(部署后台接口)
  • chrome插件postman(接口测试)
  • IE、chrome、firefox、Opera、safari、edge 6大浏览器,做兼容性测试

XMLHttpRequest发送请求步骤

  1. 实例化XMLHttpRequest对象(IE8-9是微软封装的ActiveXObject('Microsoft.XMLHTTP'))获得一个实例
  2. 通过实例open一个请求,设置发送类型和接口以及同异步
  3. 如有需要配置报文,以及各种事件(success,error,timeout等)
  4. 调用实例的send方法,发送http/https的请求
  5. 服务器回调,客户端接收,并做响应处理

关键代码

//每次清空请求缓存,并重新合并对象
var ajaxSetting = tool.checkParam(options), sendData = '';

//创建xhr对象
var xhr = tool.createXhrObject();

//针对某些特定版本的mozillar浏览器的BUG进行修正
xhr.overrideMimeType ? (xhr.overrideMimeType("text/javascript")) : (null);

//针对IE8的xhr做处理    PS:ie8下的xhr无xhr.onload事件,所以这里做判断
xhr.onload === undefined ? (xhr.xhr_ie8 = true) : (xhr.xhr_ie8 = false);

//参数处理(get和post),包括xhr.open     get:拼接好url再open   post:先open,再设置其他参数
if (ajaxSetting.data) {
    switch (ajaxSetting.contentType) {
        case '':
            tool.each(tool.MergeObject(ajaxSetting.data, ajaxSetting.publicData), function (item, index) {
                sendData += (index + "=" + item + "&")
            });
            sendData = sendData.slice(0, -1);
            ajaxSetting.requestHeader['Content-Type'] = 'application/x-www-form-urlencoded'
            break
        case 'json':
            sendData = JSON.stringify(tool.MergeObject(ajaxSetting.data, ajaxSetting.publicData))
            ajaxSetting.requestHeader['Content-Type'] = 'application/json'
            break
        case 'form':
            if (!tool.isEmptyObject(ajaxSetting.publicData)) {
                tool.each(ajaxSetting.publicData, function (item, index) {
                    ajaxSetting.data.append(index, item)
                })
            }
            sendData = ajaxSetting.data
            break
    }
    //请求前处理参数
    sendData = ajaxSetting.transformRequest(sendData)

    //判断请求类型
    if (ajaxSetting.type === 'get') {
        xhr.open(ajaxSetting.type, tool.checkRealUrl(ajaxSetting) + '?' + sendData, ajaxSetting.async)
    } else {
        xhr.open(ajaxSetting.type, tool.checkRealUrl(ajaxSetting), ajaxSetting.async)
    }
} else {
    xhr.open(ajaxSetting.type, ajaxSetting.baseURL + ajaxSetting.url, ajaxSetting.async)
}

xhr.responseType = ajaxSetting.responseType;

//设置超时时间(只有异步请求才有超时时间)
ajaxSetting.async ? (xhr.timeoutEvent = ajaxSetting.timeout) : (null);

//设置http协议的头部
tool.each(ajaxSetting.requestHeader, function (item, index) {
    xhr.setRequestHeader(index, item)
});

//onload事件(IE8下没有该事件)
xhr.onload = function (e) {
    if (this.status == 200 || this.status == 304) {
        /*
        *  ie浏览器全系列不支持responseType='json',所以在ie下使用JSON.parse进行转换
        * */
        if (ajaxSetting.responseType === 'json') {
            if (isNaN(tool.getIEVersion())) {
                ajaxSetting.successEvent(ajaxSetting.transformResponse(xhr.response));
            } else {
                ajaxSetting.successEvent(ajaxSetting.transformResponse(JSON.parse(xhr.responseText)));
            }
        } else {
            ajaxSetting.successEvent(ajaxSetting.transformResponse(xhr.response));
        }
    } else {
        /*
         *  这边为了兼容IE8、9的问题,以及请求完成而造成的其他错误,比如404等
         *   如果跨域请求在IE8、9下跨域失败不走onerror方法
         *       其他支持了Level 2 的版本 直接走onerror
         * */
        ajaxSetting.errorEvent(e.currentTarget.status, e.currentTarget.statusText);
    }
};

//xmlhttprequest每次变化一个状态所监控的事件(可拓展)
xhr.onreadystatechange = function () {
    switch (xhr.readyState) {
        case 1://打开
            //do something
            break;
        case 2://获取header
            //do something
            break;
        case 3://请求
            //do something
            break;
        case 4://完成
            //在ie8下面,无xhr的onload事件,只能放在此处处理回调结果
            if (xhr.xhr_ie8) {
                if (xhr.status == 200 || xhr.status == 304) {
                    if (ajaxSetting.responseType == "json") {
                        ajaxSetting.successEvent(ajaxSetting.transformResponse(JSON.parse(xhr.responseText)))
                    }
                }
            }
            break;
    }
    ;
};

//ontimeout超时事件
xhr.ontimeout = function (e) {
    ajaxSetting.timeoutEvent("000000", e ? (e.type) : ("timeoutEvent"));   //IE8 没有e参数
    xhr.abort();  //关闭请求
};

//错误事件,直接ajax失败,而不走onload事件
xhr.onerror = function (e) {
    ajaxSetting.errorEvent(e)
    //错误日志上传
    // tool.uploadAjaxError({
    //     errInfo: e.currentTarget.statusText,
    //     errUrl: tool.checkRealUrl(ajaxSetting),
    //     errLine: e.currentTarget.status,
    //     Browser: navigator.userAgent
    // })
};

//发送请求
xhr.send(ajaxSetting.type === 'get' ? '' : sendData);

测试代码

前端同源测试代码

ajax.post("/api/ajax1/ajaxT1/",{"name":"测试异步post请求","age":"success"},function(data){alert(data)});  //该接口在1122上

前端跨域测试代码

ajax.post("http://192.168.0.3:2211/api/weixin/ajaxT2/",{"name":"测试跨域post请求","age":"success"},function(data){alert(data)});

后端跨域接口代码

/// <summary>
/// 测试跨域请求
/// </summary>
/// <param name="module"></param>
/// <returns></returns>
[Route("ajaxT2")]
public String kuaAjaxT2([FromBody]TModule module)
{
    String result = "跨域post传输成功:"+module.name+"-"+module.age;
    return result;
}

后端同源测试代码

/// <summary>
/// 测试ajax同源请求
/// </summary>
/// <param qwer="code"></param>
/// <returns>result</returns>
[Route("ajaxT2")]
public String GetkuaAjaxT1(string name,string age)
{
    String result = "1J跨域成功:" + name + "-" + age;
    return result;
}

以下是各种浏览器的测试结果(仅提供同源post请求和跨域post请求)

同源测试
chrome

IE8-9

IE10+

firefox

opera

safari

edge

跨域测试
chrome

IE8-9

IE10+

firefox

opera

safari

edge

1.4 版本更新 --- 轮询技术的实现(需要后台接口支持)

/*
 * 长轮询的实现
 *   param: type  请求类型
 *          url   请求接口地址
 *          data  请求参数
 *          successEvent(data,this)     成功事件处理  如果得到正确数据,则让轮询停止,则在第二个回调参数设置stop属性就好
 *          timeFrequency               每隔多少时间发送一次请求
 *          errorEvent                  错误事件
 *          timeoutEvent                超时处理
 * */
longPolling: function (type, url, data, successEvent, timeFrequency, errorEvent, timeoutEvent) {
    var ajaxParam = {
        type: type,
        url: url,
        data: data,
        successEvent: function (dateCall) {
            successEvent(dateCall, this);
            if (!this.stop) {
                setTimeout(function () {
                    tempObj.longPolling(type, url, data, successEvent, timeFrequency, errorEvent, timeoutEvent);
                }, timeFrequency);
            }
            ;
        },
        //如果走了error说明该接口有问题,没必要继续下去了
        errorEvent: errorEvent,
        timeoutEvent: function () {
            timeoutEvent();
            setTimeout(function () {
                tempObj.longPolling(type, url, data, successEvent, timeFrequency, errorEvent, timeoutEvent)
            }, timeFrequency);
        }
    };
    ajax.common(ajaxParam);
},

考虑到业务需求

聊天系统会要一直需求轮询,不间断的向后台使用数据,所以isAll = true
等待付款业务只需要得到后台一次响应是否支付成功,所以使用回调参数中的第二个参数的stop属性,结束轮询

1.5.0版本更新 --- ajax的上传文件技术

/*
 *   ajax上传文件 -- level2的新特性,请保证你的项目支持新的特性再使用
 *       url                 文件上传地址
 *       fileSelector        input=file 选择器
 *       size                文件限制大小
 *       fileType            文件限制类型 mime类型
 *       successEvent             上传成功处理
 *       errorEvent               上传失败处理
 *       timeoutEvent             超时处理
 *
 *   return: status:  0      请选择文件
 *                    1      超出文件限制大小
 *                    2      非允许文件格式
 * */
upload: function (url, fileSelector, size, fileType, successEvent, errorEvent, timeoutEvent) {
    var formdata = new FormData(), fileNode = document.querySelector(fileSelector),
        fileCount = fileNode.files.length, data = {}, result = {};
    //以下为上传文件限制检查
    if (fileCount > 0) {
        tool.each(Array.prototype.slice.call(fileNode.files), function (value) {
            //检查文件大小
            if (value.size > size) {
                result["status"] = 1;
                result["errMsg"] = "超出文件限制大小";
            } else {
                if (fileType != "*") {
                    //检查文件格式.因为支持formdata,自然支持数组的indexof(h5)
                    if (fileType.indexOf(value.type) === -1) {
                        result["status"] = 2;
                        result["errMsg"] = "非允许文件格式";
                    } else {
                        formdata.append(value.name, value);
                    }
                    ;
                } else {
                    formdata.append(value.name, value);
                }
            }
            ;
        });
    } else {
        result["status"] = 0;
        result["errMsg"] = "请选择文件";
    }
    ;

    if (result.status !== undefined) return result;   //如果有错误信息直接抛出去,结束运行

    var ajaxParam = {
        type: "post",
        url: url,
        data: formdata,
        contentType: 'form',
        successEvent: successEvent,
        errorEvent: errorEvent,
        timeoutEvent: timeoutEvent
    };
    ajax.common(ajaxParam);
},

如果想要看文件上传具体内容和测试各种结果,请转到这片博客:http://www.cnblogs.com/GerryOfZhong/p/6274536.html

1.5.1版本更新 --- ajax的大文件/超大文件上传技术 (需后台配合)

/*
 *   ajax大文件切割上传(支持单个文件)  -- level2的新特性,请保证你的项目支持新的特性再使用
 *       url                 文件上传地址
 *       fileSelector        input=file 选择器
 *       cutSize             切割文件大小
 *       fileType            文件限制类型 mime类型
 *       successEvent        上传成功处理
 *       progressEvent       上传进度事件
 *       errorEvent          上传失败处理
 *       timeoutEvent        超时处理事件
 *
 *   return: status:  0      请选择文件
 *                    1      非允许文件格式
 * */
upload_big: function (url, fileSelector, cutSize, fileType, successEvent, progressEvent, errorEvent, timeoutEvent) {
    var file = document.querySelector(fileSelector).files, result = {};
    //以下为上传文件限制检查
    if (file.length === 1) {
        if (fileType != "*") {
            if (fileType.indexOf(file.type) === -1) {
                result["status"] = 1;
                result["errMsg"] = "非允许文件格式";
            }
        }
    } else {
        result["status"] = 0;
        result["errMsg"] = "请选择文件/只能上传一个文件";
    }
    ;

    if (result.status !== undefined) return result;   //如果有错误信息直接抛出去,结束运行

    //判断上传文件是否超过需要切割的大小
    if (file[0].size > cutSize) {
        var fileArr = tool.cutFile(file[0], cutSize);  //切割文件
        cutFile_upload(fileArr);
    } else {
        var fileForm = new FormData();
        fileForm.append("name", file[0].name);
        fileForm.append("file".name, file[0]);
        fileForm.append("count", 1);
        fileForm.append("cutSize", cutSize);
        var ajaxParam = {
            type: "post",
            url: url,
            data: fileForm,
            contentType: 'form',
            successEvent: function (data) {
                successEvent(data);
                progressEvent(1, 1);
            },
            errorEvent: errorEvent,
            timeoutEvent: timeoutEvent
        };
        ajax.common(ajaxParam);
    }
    ;

    /*
     *   切割文件上传,配合后台接口进行对接
     *       传输参数:
     *           count   -- 当前传输part的次数
     *           name    -- 做过处理的文件名称
     *           file    -- 上传的.part的切割文件
     *           isLast  -- 是否为最后一次切割文件上传(默认值:"true"  字符串,只有最后一次才附加)
     * */
    function cutFile_upload(fileArr, count) {
        var formData = new FormData();
        if (count == undefined) {
            count = 0;
            formData.append("name", fileArr[0].name);
            formData.append("file".name, fileArr[0].file);
        } else {
            if (count === fileArr.length - 1) {
                formData.append("isLast", "true")
            }
            ;
            formData.append("name", fileArr[count].name);
            formData.append("file".name, fileArr[count].file);
        }
        formData.append("count", count);
        formData.append("cutSize", cutSize);
        ;
        var ajaxParam = {
            type: "post",
            url: url,
            data: formData,
            contentType: 'form',
            successEvent: function (data) {
                /*
                 *   data 参数设置  需要后台接口配合
                 *       建议:如果后台成功保存.part文件,建议返回下次所需要的部分,比如当前发送count为0,则data返回下次为1。
                 *             如果保存不成功,则可false,或者返回错误信息,可在successEvent中处理
                 *
                 * */
                progressEvent(count + 1, fileArr.length);   //上传进度事件,第一个参数:当前上传次数;第二个参数:总共文件数

                var currCount = Number(data);
                if (currCount) {
                    if (currCount != fileArr.length) {
                        cutFile_upload(fileArr, currCount);
                    }
                    ;
                }
                ;
                successEvent(data);  //成功处理事件
            },
            errorEvent: errorEvent,
            timeoutEvent: timeoutEvent
        };
        ajax.common(ajaxParam);
    }
}

如果想要看文件上传具体内容和测试各种结果,请转到这片博客:http://www.cnblogs.com/GerryOfZhong/p/6295211.html

备注:ajax的上传技术,在es5+之后支持,浏览器的兼容性就是除了IE10以下,大部分都支持了

1.6版本更新 --- 集成promise规范,更优雅操作异步(主要增加了promise代码)

createPromise:function(){
    var newPromise = function(fn){
        var promise = this;
        //状态机的状态
        var PROMISESTATE = {
            PENDING : 0 ,
            FULFILLED : 1 ,
            REJECTED : 2
        };
        //存储当前变量的回调函数和标记对象为promise
        promise._fullCalll =[],promise._rejCall = [];promise._name = "promise";
        //执行过程中的状态变化(初始化状态为默认状态)
        var _state =  PROMISESTATE.PENDING;
        //回调函数的参数
        var _value = undefined;

        //状态变更
        function setState(stateT,valueT){
            var promise = this;
            _state = stateT;
            _value = valueT;
            handleFun.call(promise);  //传递作用域,并且执行回调函数
        };

        //根据状态处理回调
        function handleFun(){
            var promise = this,isThen;

            if (_state === PROMISESTATE.FULFILLED &&
                typeof promise._fullCalll[0] === 'function') {
                isThen = promise._fullCalll[0](_value);
            };
            if (_state === PROMISESTATE.REJECTED &&
                typeof promise._rejCall[0] === 'function') {
                isThen = promise._rejCall[0](_value);
            };
            //对于是否可以继续进行then做判断
            //  1. 不可then的,直接return结束(条件:无返回值、返回值不是promise对象的)
            //  2. 对于可以then的,将then的回调进行处理,然后对象之间传递。
            if (isThen === undefined || !(typeof isThen === 'object' && isThen._name === 'promise')) return;

            promise._fullCalll.shift(); promise._rejCall.shift();      //清除当前对象使用过的对调
            isThen._fullCalll =promise._fullCalll;isThen._rejCall = promise._rejCall;  //将剩下的回调传递到下一个对象
        };

        //promimse入口
        function doResolve(fn){
            var promise = this;
            fn(function(param) {
                setState.call(promise,PROMISESTATE.FULFILLED,param);
            }, function(reason) {
                setState.call(promise,PROMISESTATE.REJECTED,reason);
            });
        };

        //函数then,处理回调,返回对象保证链式调用
        this.then = function(onFulfilled,onRejected) {
            this._fullCalll.push(onFulfilled);
            this._rejCall.push(onRejected);
            return this;
        }

        doResolve.call(promise,fn);
    };
    window.Promise = newPromise;
},

如果想要看文件上传具体内容和测试各种结果,请转到这片博客:http://www.cnblogs.com/GerryOfZhong/p/7096792.html

1.7版本更新 --- 全局配置、请求格式拓展和优化、请求二进制类型、浏览器错误搜集以及npm打包发布

由于内容比较多,比较难粘贴代码,如果想要看具体内容和测试各种结果,请转到博客
博客园:http://www.cnblogs.com/GerryOfZhong/p/7575820.html
知乎:https://zhuanlan.zhihu.com/p/29640921

1.8版本更新 --- 增加请求错误监控、前端负载均衡、宕机切换、以及迭代问题修复

此次对于请求的错误监控进行了搜集,前端的负载均衡,将流量打到不同的服务器上,以及在请求宕机之后的备份切换 每次迭代都会对新的代码做测试讲解,所以篇幅比较多,so
博客园:https://www.cnblogs.com/GerryOfZhong/p/9245510.html
知乎:https://zhuanlan.zhihu.com/p/38685979

1.9版本更新 --- 设计请求池,复用请求,让前端通信快、更快、再快一点

本次包括了设计阶段,摸索阶段,实现阶段,最后全面覆盖测试阶段(包括数据搜集清洗),还有与主流前端通信框架进行对比PK阶段。
博客园:https://www.cnblogs.com/GerryOfZhong/p/9630569.html 知乎:https://zhuanlan.zhihu.com/p/44477183

具体代码已封装成一个js库,下面为API库

  • 全局配置参数 -- ajax.config
  • get请求 -- ajax.get
  • post请求 -- ajax.post
  • post(参数为json)请求 -- ajax.postJSON
  • promise请求 -- ajax.promiseAjax
  • postForm请求 -- ajax.postFormData
  • 获取blob数据集 -- ajax.obtainBlob
  • 轮询请求 -- ajax.longPolling
  • 上传文件请求 -- ajax.upload
  • 大文件切割上传请求 -- ajax.upload_big
  • 通用配置请求 -- ajax.common

最近在研究原声js的ajax的技术和设计方案,总体说来还是有很大的收获的,对浏览器的了解,js的了解,服务器技术的了解,后端的温习还是有很大的进步的,特别是解决问题的能力,感觉又上了一个level。技术的未来,不会远...

版本更新介绍

  1. 跨域不需要在前端设置跨域请求报文头,现已删除 ==>author:keepfool from cnblog
  2. 更新tool一些方法,拥抱es5+新技术 ==>author:pod4g from github
  3. 删除头部参数中的跨域参数设置 ==>author:wYhooo from github
  4. 集成ajax的轮询技术
  5. 集成ajax的上传文件技术,也更新了一些原来的代码
    a. 增加FormData数据传输方法
    b. 新增各种类型判断方法判断类型
    c. 更新each方法,判断如果传入参数obj为数组而且浏览器支持es5的新特性直接用数组的forEach方法
  6. 更新bug,更细ajax默认值相互影响问题,调试ajax长轮询bug
  7. 集成promise规范,更优雅操作异步
  8. 全局配置、请求格式拓展和优化、请求二进制类型、浏览器错误搜集以及npm打包发布

程序员的小笑话

个人介绍