基於 TS 實現 axios(八)

這一章主要完善接口
目錄結構沒有進行修改

withCredentials

這個功能是可以攜帶跨越請求的,默認情況下是自動攜帶同源 cookie 的 ,但是跨域的時候是不可以進行攜帶的,將 withCredentials 設置成 true 就可以進行攜帶了。

types/index.ts

export interface AxiosRequestConfig {
	// ...

    withCredentials?:boolean,
	
    //...
}

core/xhr.ts

// ...
if(timeout) request.timeout = timeout;

if(withCredentials) {
    request.withCredentials = withCredentials;
}
// ...

接改變這點就夠了

XSRF

其實就是在 header 中添加 token (後端生成返回的標識)

types/index.ts

export interface AxiosRequestConfig {
	// ...
    xsrfCookieName?:string,
    xsrfHeaderName?:string,
	// ...
}

defaults.ts

const defaults: AxiosRequestConfig = {
	// ...
    xsrfCookieName: 'XSRF-TOKEN',
    xsrfHeaderName: 'X-XSRF-TOKEN',
	// ...
}

helpers/url.ts

interface URLOrigin{
    protocol:string,
    host: string,
}


export function isURLsameOrigin(requestURL: string):boolean {
    const parseOrigin = resolverURL(requestURL);
    return (parseOrigin.protocol === currentOrigin.protocol && parseOrigin.host === currentOrigin.host)

}

const urlParsingNode = document.createElement('a');
const currentOrigin = resolverURL(window.location.href);


function resolverURL(url: string): URLOrigin {
    urlParsingNode.setAttribute('href',url);

    const {protocol,host} = urlParsingNode;

    return {
        protocol,
        host,
    }
}

通過 a 標籤拿到 protocol (協議)和 host (主機地址)

新建 helpers/cookie.ts

const cookie = {
    read(name: string) : string | null {
        const match = document.cookie.match(new RegExp('(^|;\\s*)(' +  name + ')=([^;]*)'));
        return match ? decodeURIComponent(match[3]) : null;
    }
}

export default cookie;

讀取 cookie

core/xhr.ts

// ...
request.ontimeout = function handleTimeout() {
    reject(createError(`Timeout of ${timeout} ms exceeded`,config,'ECONNABORTED',request));
}
// 添加的部分
if((withCredentials || isURLsameOrigin(url)) && xsrfCookieName){
    const xsrfValue = cookie.read(xsrfCookieName);
    if(xsrfValue && xsrfHeaderName) {
        headers[xsrfHeaderName] = xsrfValue;
    }
};
// 添加的部分

// ...

自動設置 cokie ,個人感覺沒什麼用

下載 和 上傳監控

types/index.ts

// ...
export interface AxiosRequestConfig {
	// ...

    onDownloadProgress?:(e: ProgressEvent) => void,
    onUploadProgress?: (e: ProgressEvent) => void,
	// ...
}
// ...

helpers/util.ts

export function isFormData(val: any): val is FormData {
    return typeof val !== 'undefined' && val instanceof FormData
}

判斷對象是否是 FormData

core/xhr.ts

// ... 錨點
request.ontimeout = function handleTimeout() {
    reject(createError(`Timeout of ${timeout} ms exceeded`,config,'ECONNABORTED',request));
}

// 添加的部分
if(onDownloadProgress) {
    request.onprogress = onDownloadProgress;
}        

if(onUploadProgress) {
    request.upload.onprogress = onUploadProgress;
}

if(isFormData(data)) {
    delete headers['Content-Type']
} 
// 添加的部分

// ...

將 上傳 和 下載的函數掛載到 request 上;如果 dataFormdata 類型的數據就將 Content-Type 刪除,瀏覽器會默認添加的

整理核心庫 xhr

import {AxiosRequestConfig,AxiosPromise,AxiosResponse} from '../types';
import {parseHeaders} from '../helpers/header';
import {createError} from '../helpers/error';
import {isURLsameOrigin} from '../helpers/url';
import {isFormData} from '../helpers/util'
import cookie from '../helpers/cookie';

export default function xhr(config:AxiosRequestConfig) : AxiosPromise {
    return new Promise((reslove,reject)=> {
        const {
            data = null,
            url,
            method='get',
            timeout,
            headers,
            responseType,
            cancleToken,
            withCredentials,
            xsrfCookieName,
            xsrfHeaderName,
            onDownloadProgress,
            onUploadProgress
        } = config;
        const request = new XMLHttpRequest();
        
        request.open(method.toUpperCase(),url!,true);

        configureRequest();

        addEvents();

        processHeaders();

        proccessCancel();

        request.send(data);


        function configureRequest(): void{
            if(responseType) request.responseType = responseType;

            // 超時操作
            if(timeout) request.timeout = timeout;
        
            if(withCredentials) request.withCredentials = withCredentials;
        }

        function addEvents():void{
            
            request.onreadystatechange = function handleLoad() {
                if(request.readyState !== 4) {
                    return;
                }

                if(request.status === 0) {
                    // 網絡錯誤和超時錯誤時 status爲 0
                    return;
                }

                const responseHeaders = parseHeaders(request.getAllResponseHeaders());
                const responseDate = responseType !== 'text' ? request.response : request.responseText;
                const response:AxiosResponse = {
                    data:responseDate,
                    status:request.status,
                    statusText: request.statusText,
                    headers: responseHeaders,
                    config,
                    request,
                }
                handleResponse(response);
            }
 
            // 請求錯誤
            request.onerror = function handleError() {
                reject(createError('Network Error',config,null,request));
            } 
            // 超時錯誤
            request.ontimeout = function handleTimeout() {
                reject(createError(`Timeout of ${timeout} ms exceeded`,config,'ECONNABORTED',request));
            }

            if(onDownloadProgress) {
                request.onprogress = onDownloadProgress;
            }        

            if(onUploadProgress) {
                request.upload.onprogress = onUploadProgress;
            }
        }

        function processHeaders():void {
            if(isFormData(data)) {
                delete headers['Content-Type']
            } 
    
            if((withCredentials || isURLsameOrigin(url!)) && xsrfCookieName){
                console.log('執行了',xsrfCookieName);
                const xsrfValue = cookie.read(xsrfCookieName);
                console.log(xsrfValue);
                if(xsrfValue && xsrfHeaderName) {
                    headers[xsrfHeaderName] = xsrfValue;
                }
            };
    
            // 錯誤結束        
            Object.keys(headers).forEach((name) => {
                if(data === null && name.toLocaleLowerCase() === 'content-type') {
                    delete headers[name];
                } else {
                    request.setRequestHeader(name,headers[name]);
                }
            })
        }

        function proccessCancel():void {
            if(cancleToken) {
                cancleToken.promise.then(reason => {
                    request.abort()
                    reject(reason)
                })
            }
        }


        function handleResponse(response: AxiosResponse) : void{
            if(response.status >= 200 && response.status < 300) {
                reslove(response);
            } else {
                reject(createError(`Request failed with status code ${response.status}`,config,null,request,response));
            }
        }
    })

}

從以上代碼可以看出,我將以前的代碼簡單的歸了類, 依次執行 configureRequestaddEventsprocessHeadersproccessCancel 這四個函數,組合在進行發送

HTTP auth

HTTP協議中的 Authorization 請求消息頭含有服務器用於驗證用戶代理身份的憑證,通常會在服務器返回401 Unauthorized 狀態碼以及WWW-Authenticate 消息頭之後在後續請求中發送此消息頭。

axios 中可以通過 auth 來設置 Authorization ,個人感覺沒太大用。

types/index.ts

// ...
export interface AxiosRequestConfig {
    // ...
    auth?:AxiosBasicCredentials,
    // ...
}
// ...
export interface AxiosBasicCredentials {
    username: string,
    password: string,
}

core/xhr.ts

if(auth) {
    headers['Authorization'] = 'Basic ' + `${btoa(auth.username)}:${btoa(auth.password)}`;
}

就這樣就行了,不過使用的話還是要配合後臺的。

自定義合法狀態碼

我們默認的合法狀態碼( status )是 200 - 300,我們可以通過 validateStatus 函數來自定義合法狀態

types/index.ts

export interface AxiosRequestConfig {
	// ...
    validateStatus?:(status:number) => boolean,
	// ...
}

core/xhr.ts

function processHeaders():void {
    // ...
    if(auth) {
        headers['Authorization'] = 'Basic ' + `${btoa(auth.username)}:${btoa(auth.password)}`;
    }

    // ...
}

使用

axios.get('/more/304',{
  validateStatus(status){
    return status >= 200 && status < 400
  }
}).then(res => {
  console.log(res)
}).catch((e) =>{
  console.log(e.message);
})

自定義參數解析規則

看名字應該理解,用戶自己定義 url 產生的解析規則

types/index.ts

export interface AxiosRequestConfig {
	// ...
    paramsSerializer?:(params: any) => string,
	// ...
}

helpers/url.ts

export function buildURL(url: string, params?: any, paramsSerializer?:(params:any) => string):string {
    if(!params) return url;

    let serializedParams;

    if(paramsSerializer) {
        serializedParams = paramsSerializer(params);
    } else if(isURLSearchParams(params)){
        serializedParams = params.toString()
    }else {
        const parts:string[] = []; 

        Object.keys(params).forEach((key) => {
            const val = params[key];
            if(val === null || typeof val === 'undefined') {
                return
            }
            let values = [];
            if(Array.isArray(val)) {
                values = val;
                key += '[]';
            }else {
                values = [val];
            }
            values.forEach((val) => {
                if(isDate(val)){
                    val = val.toISOString();
                }else if(isPlainObject(val)) {
                    val = JSON.stringify(val);
                }
                parts.push(`${encode(key)}=${encode(val)}`);
            })
        })
        serializedParams = parts.join('&');
    }

    if(serializedParams) {
        const markIndex = url.indexOf('#');
        if(markIndex !== -1) {
            url = url.slice(0,markIndex);
        }
        url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams;

    }
    return url;
}

這個函數分三種情況:paramsSerializer 有的話,就進行自定義的參數解析; params 是否是 URLSearchParams ;默認解析情況

helpers/util.ts

export function isURLSearchParams(val: any): val is URLSearchParams{
    return typeof val !== 'undefined' && val instanceof URLSearchParams
}

core/dispatchRequest.ts

function transformURL (config: AxiosRequestConfig) :string {
    const {url,params,paramsSerializer} = config;
    return buildURL(url!,params,paramsSerializer);
}

baseURL

就是我們訪問同一個域名下的多個接口時,我們不希望每次發送請求都填寫完整的 url,可以用這個參數進行配合

types/index.ts

export interface AxiosRequestConfig {
    baseURL?:string,
}

helpers/url.ts

export function isAbsoluteURL(url:string): boolean {
    return /(^[a-z][a-z\d\+\-\.]*:])?\/\//i.test(url)
}

export function combineURL(baseURL:string, relativeURL?:string):string {
    return relativeURL ? baseURL.replace(/\/+$/,'') + '/' +relativeURL.replace(/^\/+/, ''): baseURL
}

前一個函數用來判斷是否是絕對url,後一個函數進行url地址連接

core/dispatchRequest.ts

function transformURL (config: AxiosRequestConfig) :string {
    let {url,params,paramsSerializer,baseURL} = config;
    if(baseURL && !isAbsoluteURL(url)) {
        url = combineURL(baseURL,url);
    }
    return buildURL(url!,params,paramsSerializer);
}

這裏進行完整的url地址拼接

我覺得這些函數已經夠用了,其他的一些很小衆的函數我就不寫了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章