[ 造輪子 ] 手動封裝 AJAX (三) —— 最終版

導言

在開始以前先想想ajax是怎樣的流程

  • 首先打開一個鏈接
  • 發送數據
  • 返回結果

咱們要自定義的設置有哪些

  • 設置請求方式
  • 設置請求頭
  • 設置返回數據格式
  • 返回成功後或失敗後

咱們要作的功能有哪些

  • 數據校驗
  • 統一數據的格式
  • 支持文件上傳
  • 對於傳入參數的容錯處理
  • 超時處理 //modify time 2019-01-01

通過以上思考基本結構大體成型

  1. 數據校驗
  2. 數據格式的統一
  3. 創建鏈接
  4. 設置請求頭
  5. 設置返回數據格式
  6. 發送數據
  7. 返回成功或失敗
  8. 超時處理

代碼以下ajax

class AJAX {
    constructor({url = "",method = "GET",data = {},async = true,success,error,resType = "",headers = {}},time = 5000) {
        //集中管理傳遞過來的參數
        this.option = {url,method,data,async,success,error,resType,headers,time};
        this.timeout;
        this.xhr = new XMLHttpRequest();
        this.start();
    }
    start() {
        //數據校驗
        this.checkOption();
        //數據格式的統一
        this.initOption();
        //創建鏈接
        this.open();
        //設置請求頭
        this.setHeaders();
        //設置返回數據格式
        this.setResponseType();
        //發送數據
        this.sendData()
        //返回成功或失敗
        this.responseData();
    };
    
}
複製代碼

接下來添加校驗功能

  • 首先url不能是空
  • 而後請求頭必須是字面量對象格式 {key:value}
  • 再有就是一些簡單的警告

代碼以下json

checkOption() {
    let {url,async,resType,headers,time} = this.option;
    if (url === '') {
        throw new Error('請求地址不能爲空'); //打印錯誤信息,並中止當前進程
        //Console.error('請求地址爲空'); 也能夠打印錯誤信息,可是不能中止當前進程
    }

    if(typeof headers !== 'object'){
        throw new Error('設置請求頭時請傳入 {key:value,key:value...} 的格式');
    }
    
    if(typeof resType !== 'string'){
        throw new Error('設置返回數據格式時請傳入字符出串格式');
    }
    
    if(typeof time !== 'number'){
        throw new Error('超時時間請傳入數字類型數據');
    }
    
    if (typeof url !== 'string') {
        //輸出警告信息
        console.warn('當前請求地址不是字符串,如今將其嘗試轉換爲字符串');
    }
    if (async === false && resType != '') {
        console.warn('若是設置了請求方式爲同步,即便設置了返回數據格式也不會生效');
    }
};
複製代碼

須要注意的是返回數據格式能夠設置這幾個值,以後會寫一個詳細的傳參指南api

TIM截圖20181229182412.png

接下來是數據的處理

  • 首先咱們須要保證請求格式,無論傳入時是大寫仍是小寫,在咱們設置請求格式時要是所有大寫
  • 還有就是url多是數字的,須要轉換成字符
  • 爲了方便將 async不是布爾型的轉成布爾型,這是什麼概念,就是傳參時 寫數字 1 是異步 數字 0 是同步
  • 將須要發送的內容作一個處理
initOption() {
    let {url,async,method} = this.option;
    //url不是字符串轉換成字符串
    if (typeof url !== 'string') {
        try {
            this.option.url = url.toString();
            console.log(`轉換成功: "${this.option.url}"`);
        } catch (error) {
            throw new Error('url 轉換字符串失敗');
        }
    }
    //async不是布爾型轉成布爾型
    if(typeof async !=='boolean'){
        async == true ? this.option.async = true : this.option.async = false;
    }

    //將 post get 轉換爲大寫
    this.option.method = method.toUpperCase();
    
    //post和get數據初始化
    if(this.option.method != 'FORMDATA'){// [1]
        let data = this.option.data;
        if(typeof data === 'object'){//[2]
            if( this.option.method === 'GET'){
                let arr=[];
                for(let name in data){
                    arr.push(`${name}=${data[name]}`);//[3]
                }
                let strData=arr.join('&');//[4]
                this.option.data=`?${strData}`;//[5]
            }else if( this.option.method === 'POST'){
                let formData = new FormData();//[6]
                for(let key in data){
                    formData.append(`${key}`,`${data[key]}`);
                }
                this.option.data=formData;
            }
            
        }else if(typeof data === 'string' && this.option.method === 'GET'){//[7]
                this.option.data=`?${data}`;
        }
   }
};
複製代碼

這裏詳細說說對須要發送數據的處理,按照序號來講

  1. 判斷它不是 formData ,也就是說是 GET 和 POST 時咱們進行數據處理,是 formData 不進行處理,直接發送,這是爲了可以實現文件上傳功能
  2. 判斷它是否是 {key:vlue} 這種格式的,是的話解析或拼接,不是的話跳到 [7] 若是是字符串直接加到 url 後邊
  3. [3] [4] [5] 這裏是爲了處理成 url?key=value$key=value 這種 url 傳參的數據格式
  4. [6] 是新建了一個 FormData 對象,是 ajax2.0 裏邊的,它最主要的能夠用 ajax 實現文件上傳功能,在這裏是爲了代碼簡單

打開鏈接

通過以前的數據處理這裏只須要判斷下是 GET 仍是其餘方式(post formdata),而後選擇對應的鏈接方式promise

open(){
        let {method,url,async,data} = this.option;
        if(method === 'GET'){
            this.xhr.open(method,url+data,async);
        }else{
            this.xhr.open(method,url,async);
        }
    }
複製代碼

設置自定義請求頭

將傳入的參數進行解析,而後設置自定義請求頭 代碼以下bash

setHeaders(){
        let headers = this.option.headers;
        for(let key in headers){
            this.xhr.setRequestHeader(`${key.toString()}`,`${headers[key].toString()}`)
        }
    }

複製代碼

設置返回數據格式、發送數據

  • 因爲同步請求時不能設置返回數據格式,因此作下判斷
  • 發送數據這裏,在通過以前的數據處理後只有 GET 方式有所區別,其餘兩種沒有區別(支持 GET POST 以及我本身定義的一種,更多請求方法可自行擴展)
setResponseType() {
    if (this.option.async) {
        this.xhr.responseType = this.option.resType;
    }
}
複製代碼

添加超時處理

sendData(){
    if(this.option.method == 'GET'){
        this.xhr.send();
    }else{
        this.xhr.send(this.option.data);
    }
    this.timeout = setTimeout(()=>{ 
        typeof this.option.error === 'function' && this.option.error('請求超時,默認超時時間爲 5000 毫秒');
        this.option.reject('請求超時,默認超時時間爲 5000 毫秒');
    }, this.option.time);
}
複製代碼

請求完成後的數據返回

  • 請求完成後會返回數據
    判斷 success 以及 error 是否是函數,是的話會將數據返回給 success 或者將錯誤信息返回給 error
responseData(){
    this.xhr.onload = ()=>{
        if(this.xhr.status >= 200 && this.xhr.status < 300 || this.xhr.status === 304){
            typeof this.option.success === 'function'  && this.option.success(this.xhr.response);
            
        }else{
            typeof this.option.error === 'function' && this.option.error(this.xhr.statusText);
            
        }
    }
}
複製代碼

在實現基本功能後,忽然想到 jQuery 的 ajax 是會返回一個 promise 對象,能夠同時使用回掉函數,或者使用 then 和 catch 來處理數據
所以修改了下傳入參數,以及返回數據的處理cookie

傳參時代碼以下app

//add resolve reject
class AJAX {
    constructor({url = "",method = "GET",data = {},async = true,success,error,resType = "",headers = {},resolve,reject}) {
        this.option = {url,method,data,async,success,error,resType,headers,resolve,reject};
        this.xhr = new XMLHttpRequest();
        this.start();
    }
}
複製代碼

返回數據時代碼以下異步

responseData(){
    this.xhr.onload = ()=>{
        if(this.xhr.status >= 200 && this.xhr.status < 300 || this.xhr.status === 304){
            clearTimeout(this.timeout);
            typeof this.option.success === 'function'  && this.option.success(this.xhr.response);
            this.option.resolve(this.xhr.response);//add
        }else{
            clearTimeout(this.timeout);
            typeof this.option.error === 'function' && this.option.error(this.xhr.statusText);
            this.option.reject(this.xhr.statusText);//add
        }
    }
}
複製代碼

最終代碼

class AJAX {
    constructor({url = "",method = "GET",data = {},async = true,success,error,resType = "",headers = {},resolve,reject}) {
        this.option = {url,method,data,async,success,error,resType,headers,resolve,reject};
        this.xhr = new XMLHttpRequest();
        this.start();
    }
    start() {
        //數據校驗
        this.checkOption();
        //數據格式的統一
        this.initOption();
        //創建鏈接
        this.open();
        //設置請求頭
        this.setHeaders();
        //設置返回數據格式
        this.setResponseType();
        //發送數據
        this.sendData()
        //返回成功或失敗
        this.responseData();
    };
    checkOption() {
        let {url,async,resType,headers} = this.option;
        if (url === '') {
            throw new Error('請求地址不能爲空'); //打印錯誤信息,並中止當前進程
            //Console.error('請求地址爲空'); 也能夠打印錯誤信息,可是不能中止當前進程
        }

        if(typeof headers !== 'object'){
            throw new Error('設置請求頭時請傳入 {key:value,key:value...} 的格式');
        }
        
         if(typeof time !== 'number'){
            throw new Error('超時時間請傳入數字類型數據');
        }
        
        if(typeof resType !== 'string'){
            throw new Error('設置返回數據格式時請傳入字符出串格式');
        }
        // ""	            與設置爲"text"相同, 是默認類型 (其實是 DOMString)
        // "arraybuffer"	將接收到的數據類型視爲一個包含二進制數據的 JavaScript ArrayBuffer 
        // "blob"	        將接收到的數據類型視爲一個包含二進制數據的 Blob 對象 
        // "document"	    將接收到的數據類型視爲一個 HTML Document 或 XML XMLDocument ,這取決於接收到的數據的 MIME 類型
        // "json"	        將接收到的數據類型視爲 JSON 解析獲得的
        // "text"	        將接收到的數據類型視爲包含在 DOMString 對象中的文本
        if (typeof url !== 'string') {
            //輸出警告信息
            console.warn('當前請求地址不是字符串,如今將其嘗試轉換爲字符串');
        }
        if (async === false && resType != '') {
            console.warn('若是設置了請求方式爲同步,即便設置了返回數據格式也不會生效');
        }
    };
    
    initOption() {
        let {url,async,method} = this.option;
        //url不是字符串轉換成字符串
        if (typeof url !== 'string') {
            try {
                this.option.url = url.toString();
                console.log(`轉換成功: "${this.option.url}"`);
            } catch (error) {
                throw new Error('url 轉換字符串失敗');
            }
        }
        //async不是布爾型轉成布爾型
        if(typeof async !=='boolean'){
            async == true ? this.option.async = true : this.option.async = false;
        }

        //將 post get 轉換爲大寫
        this.option.method = method.toUpperCase();
        
        //post和get數據初始化
        if(this.option.method != 'FORMDATA'){
            let data = this.option.data;
            if(typeof data === 'object'){
                if( this.option.method === 'GET'){
                    let arr=[];
                    for(let name in data){
                        arr.push(`${name}=${data[name]}`);
                    }
                    let strData=arr.join('&');
                    this.option.data=`?${strData}`;
                }else if( this.option.method === 'POST'){
                    let formData = new FormData();
                    for(let key in data){
                        formData.append(`${key}`,`${data[key]}`);
                    }
                    this.option.data=formData;
                }
                
            }else if(typeof data === 'string' && this.option.method === 'GET'){
                this.option.data=`?${data}`;
            }
       }
    };

    open(){
        let {method,url,async,data} = this.option;
        if(method === 'GET'){
            this.xhr.open(method,url+data,async);
        }else{
            this.xhr.open(method,url,async);
        }
    }

    setHeaders(){
        let headers = this.option.headers;
        for(let key in headers){
            this.xhr.setRequestHeader(`${key.toString()}`,`${headers[key].toString()}`)
        }
    }

    setResponseType() {
        if (this.option.async) {
            this.xhr.responseType = this.option.resType;
        }
    }

    sendData(){
        if(this.option.method == 'GET'){
            this.xhr.send();
        }else{
            this.xhr.send(this.option.data);
        }
        this.timeout = setTimeout(()=>{ 
            typeof this.option.error === 'function' && this.option.error('請求超時,默認超時時間爲 5000 毫秒');
            this.option.reject('請求超時,默認超時時間爲 5000 毫秒');
        }, this.option.time);
    }

    responseData(){
        this.xhr.onload = ()=>{
            if(this.xhr.status >= 200 && this.xhr.status < 300 || this.xhr.status === 304){
                clearTimeout(this.timeout);
                typeof this.option.success === 'function'  && this.option.success(this.xhr.response);
                this.option.resolve(this.xhr.response);//add
            }else{
                clearTimeout(this.timeout);
                typeof this.option.error === 'function' && this.option.error(this.xhr.statusText);
                this.option.reject(this.xhr.statusText);//add
            }
        }
    }

    all(promises) {
        return Promise.all(promises);
    };
}

function ajax({url,method,data,async,success,error,resType,headers,time}){
    return new Promise((resolve, reject) => {
        return new AJAX({url,method,data,async,success,error,resType,headers,time,resolve,reject});
    });
}
複製代碼

使用時能夠將代碼複製粘貼到單獨的 js 文件而後用 script 標籤引入
也能夠添加一行 export 代碼將最後的 ajax 暴露出去 使用import 引入async

具體使用方法用法函數

ajax({
    url:'api/login',
    method:'post',//支持 GET POST 和我自定義的 FORMDATA ,傳入時不區分大小寫
    data = {
        name:"yhtx",
        id:"1997"
    },//除了這種還支持字符串 "name=yhtx&id=1997";以及 formData 數據,在傳入formData 數據時請將 method 設置爲 FORMDATA
    async = true,//能夠使用數字 1 代替 true ,數字 0 代替 false
    time = 5000,//請求超時時間,默認爲 5000 毫秒
    success(res){
        //能夠使用回調的形式處理數據也能夠使用 then
    },error(err){
        //能夠使用回調的形式處理錯誤也能夠使用 catch
    },
    resType = "",//能夠傳入 "" "arraybuffer" "blob" "document" "json" "text"	
    headers = {
        mycookie: "46afqwiocibQEIJfa498./&678" //使用對象的方式傳參
    }
}).then((res)=>{
    //能夠使用 then 的形式處理數據也能夠使用回調函數
}).catch((err)=>{
    //能夠使用 catch 的形式處理數據也能夠使用回調函數
})
複製代碼
相關文章
相關標籤/搜索