第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

Ajax 封裝

前言

學(xué)會(huì)了 Ajax 的請(qǐng)求以及如何處理服務(wù)端的響應(yīng)。這一章節(jié),我們著重來(lái)封裝一個(gè)簡(jiǎn)單的 Ajax。

前置知識(shí):

  1. 本章節(jié)會(huì)使用部分 ES6 語(yǔ)法
  2. 本章節(jié)使用 Promise

簡(jiǎn)單需求:

  • 支持 Promise 語(yǔ)法處理結(jié)果
  • 支持自定義配置,包括 headers
  • 內(nèi)置 url、params、 data、headers 處理

1. 構(gòu)造一個(gè)這樣的 xhr

function xhr(config) {
    return new Promise((resolve, reject) => {
        const request = new XMLHttpRequest();

        /**
     	* 調(diào)用 open 方法
     	*/
        request.open(method, url);

        request.onreadystatechange = function handleLoad() {
            if (request.readyState !== 4) return
            if (request.status === 0) return
            const responseData = request.response
            resolve(responseData)
        }

        request.send(data)
    });
}

首先, 我們的 xhr 函數(shù)支持 config 傳入, 內(nèi)部通過(guò) XMLHttpRequest 技術(shù)來(lái)進(jìn)行請(qǐng)求的收發(fā), 大致就是上面這樣結(jié)構(gòu)的代碼,內(nèi)部的實(shí)現(xiàn)我們前面章節(jié)都講過(guò),唯一不同的是,在 onreadystatechange 上,我們掛載的方法最后使用 resolve() 來(lái)進(jìn)行斷言,這樣做的目的是,后續(xù)可以通過(guò) .then() 的方式進(jìn)行數(shù)據(jù)操作。

1.1 method 標(biāo)準(zhǔn)化

首先, 用戶傳進(jìn)來(lái)的 method 可能是大寫也可能是小寫,我們可以先做一個(gè)標(biāo)準(zhǔn)化,對(duì) method 做一個(gè)轉(zhuǎn)化,將其變?yōu)榇髮懀?/p>

method.toUpperCase()

1.2 構(gòu)建 url

有些同學(xué)很奇怪,為什么說(shuō)構(gòu)建 url,我們不是通過(guò) config 傳入 url 嗎?

是的,但是同學(xué)你別忘了,我們支持 params!

因此,我們需要把 params 上的參數(shù)進(jìn)行一定格式序列化拼接到 url 后面 ,構(gòu)成 "url?a=xxx&b=xxx" 的格式。為此,我們需要提供了一個(gè) buildUrl 的函數(shù):

/**
 * 構(gòu)建 url
 * @param {*} url
 * @param {*} params
 */
function buildUrl(url, params) {
    if (!params || !isPlainObject(params)) return url; // 如果 params 沒(méi)有傳或者不是一個(gè)純對(duì)象,直接返回原 url
    let values = [];
    Object.keys(params).forEach(key => {
        // 對(duì) params 中的每一項(xiàng)進(jìn)行處理
        const val = params[key];
        if (typeof val === undefined || val === null) {
            // 如果當(dāng)前項(xiàng)的值為 undefined 或者 null,則忽略
            return;
        }
        values.push(`${key}=${val}`); // 將 “key=value”的形式加入到 values 數(shù)組中
    });
    let serializedParams = values.join("&"); // 序列化,將 values 數(shù)組轉(zhuǎn)化為字符串,格式為 "key=value&key=value"
    if (serializedParams) {
        // 如果有值,則加入到url后面。構(gòu)成 "url?key=value&key=value" 的形式
        url += (url.indexOf("?") === -1 ? "?" : "&") + serializedParams;
    }
    return url;
}

在這個(gè)函數(shù)中,我們可以傳參 url 和 params。如果傳入params 為假值,那我們直接忽略,返回 url 即可。否則,我們需要對(duì) params 中 的每一項(xiàng)目進(jìn)行序列化,變?yōu)?"key=vaue" 這樣的形式, 添加到 values 數(shù)組中。接著我們通過(guò)數(shù)組的 .join("&") 的方法,把 values 數(shù)組通過(guò) “&” 進(jìn)行拼接。最后拼接到 url 后面,構(gòu)成 "url?key=value&key=value" 的形式返回。

這里,我們也涉及到了一個(gè)工具函數(shù) isPlainObject,在本章節(jié)中好幾處都會(huì)用到,他的作用是判斷該對(duì)象是不是一個(gè)純 “{}” 的對(duì)象,它的實(shí)現(xiàn)如下:

const toString = Object.prototype.toString; // 由于 Object.prototype.toString 在判斷類型的時(shí)候非常好用,并且用到的次數(shù)經(jīng)常會(huì)比較多,我們通??梢赃@樣緩存起來(lái)

/**
 * 判斷當(dāng)前 val 是否是一個(gè)純對(duì)象
 * @param {*} val
 */
function isPlainObject(val) {
    return toString.call(val) === "[object Object]";
}

1.3 標(biāo)準(zhǔn)化 data

因?yàn)?.send() 是無(wú)法支持 Json 格式數(shù)據(jù)的,所以我們需要對(duì) data 做一個(gè)序列化處理:

/**
 * 處理 data,因?yàn)?send 無(wú)法直接接受 json 格式數(shù)據(jù),這里我們可以直接序列化之后再傳給服務(wù)端
 * @param {*} data 
 */
function transformData (data) {
    if (isPlainObject(data)) {
        return JSON.stringify(data)
    }
    return data
}

實(shí)現(xiàn)非常簡(jiǎn)單,如果判斷 data 是一個(gè)純對(duì)象的話,就加一道 JSON.stringify(data) 的操作進(jìn)行序列化, 否則直接返回 data 本身。

1.4 設(shè)置 headers

對(duì)于 headers 的操作,我們會(huì)著重對(duì) Content-Type 進(jìn)行處理,在沒(méi)有 Content-Type 的時(shí)候,我們應(yīng)該有個(gè)默認(rèn)的支持。因?yàn)?headers 屬性上是大小寫不敏感的,因此我們會(huì)對(duì) Content-Type 做一個(gè)統(tǒng)一處理:

function transformHeaders (headers) {
    const contentTypeKey = 'Content-Type' // Content-Type 的 key 值常量
    if (isPlainObject(headers)) {
        Object.keys(headers).forEach(key => {
            if (key !== contentTypeKey && key.toUpperCase() === contentTypeKey.toLowerCase()) {
                // 如果 key 的大寫和 contentTypeKey 的大寫一致,證明是同一個(gè),這時(shí)就可以用 contentTypeKey 來(lái)替代 key 了
                headers[contentTypeKey] = headers[key]
                delete headers[key]
            }
        })
        if (!headers[contentTypeKey]) {
            // 如果最后發(fā)現(xiàn)沒(méi)有 Content-Type,那我們就設(shè)置一個(gè)默認(rèn)的
            headers[contentTypeKey] = 'application/json;charset=utf-8'
        }
    }
}

// 在 function xhr 中
// 設(shè)置頭部
transformHeaders(headers)
Object.keys(headers).forEach(key => {
    if (!data && key === 'Content-Type') {
        delete headers[key]
        return
    }
    request.setRequestHeader(key, headers[key])
})

transformHeaders 函數(shù)對(duì) headers 進(jìn)行了一定程度的轉(zhuǎn)化,包括為 Content-Type 提供了默認(rèn)的支持,這里默認(rèn)為 "application/json;charset=utf-8"。在 xhr 函數(shù)中,我們還會(huì)對(duì)headers的每一項(xiàng)進(jìn)行判斷,如果沒(méi)有 data ,那我們會(huì)刪除 Content-Type。同時(shí),我們會(huì)調(diào)用 setRequestHeader 方法將 headers 屬性添加到頭部。

1.5 設(shè)置響應(yīng)類型

if (responseType) {
    // 如果設(shè)置了響應(yīng)類型,則為 request 設(shè)置 responseType
    request.responseType = responseType;
}

1.6 設(shè)置超時(shí)時(shí)間

if (timeout) {
    // 如果設(shè)置超時(shí)時(shí)間, 則為 request 設(shè)置 timeout
    request.timeout = timeout;
}

1.7 處理結(jié)果

// 狀態(tài)變化處理函數(shù)
request.onreadystatechange = function handleLoad() {
    if (request.readyState !== 4) return;
    if (request.status === 0) return;
    
    // 獲取響應(yīng)數(shù)據(jù)
    const responseData =
          request.responseType === "text"
    ? request.responseText
    : request.response;
    if (request.status >= 200 && request.status < 300 || request.status === 304) {
        // 成功則 resolve 響應(yīng)數(shù)組
        resolve(responseData);
    } else {
        // 失敗則 reject 錯(cuò)誤原因
        reject(new Error(`Request failed with status code ${request.status}`));
    }
};

// 錯(cuò)誤處理事件
request.onerror = function hadleError() {
    //reject 錯(cuò)誤原因
    reject(new Error('Network Error'))
}

// 超時(shí)處理事件
request.ontimeout = function handleTimeout() {
    // reject 錯(cuò)誤原因
    reject(new Error(`Timeout of ${timeout} ms exceeded`))
}

處理結(jié)果分為幾個(gè)部分:

  1. 正常處理服務(wù)端響應(yīng)
  2. 請(qǐng)求錯(cuò)誤
  3. 請(qǐng)求超時(shí)

其中,正常處理服務(wù)端響應(yīng)還要判斷狀態(tài)碼,這里判斷正確的是 200 至 300 之間狀態(tài)碼,再一個(gè)是 304 緩存。此時(shí)我們會(huì)通過(guò) resolve 斷言數(shù)據(jù)。否則,通過(guò) reject 來(lái)斷言失敗原因。

1.8 xhr 函數(shù)

至此,我們會(huì)得到這樣一個(gè) xhr 函數(shù):

function xhr(config) {
    return new Promise((resolve, reject) => {
        const {
            url,
            method = "get",
            params = {},
            data = null,
            responseType,
            headers,
            timeout
        } = config;
        const request = new XMLHttpRequest();

        /**
     * 調(diào)用 open 方法
     * method.toUpperCase() 的作用主要是講 method 都標(biāo)準(zhǔn)統(tǒng)一為大寫字母狀態(tài)。 比如 'get'.toUpperCase() 會(huì)返回 'GET'
     */
        request.open(method.toUpperCase(), buildUrl(url, params));

        if (responseType) {
            // 如果設(shè)置了響應(yīng)類型,則為 request 設(shè)置 responseType
            request.responseType = responseType;
        }

        if (timeout) {
            // 如果設(shè)置超時(shí)時(shí)間, 則為 request 設(shè)置 timeout
            request.timeout = timeout;
        }

        // 設(shè)置頭部
        transformHeaders(headers);
        Object.keys(headers).forEach(key => {
            if (!data && key === "Content-Type") {
                delete headers[key];
                return;
            }
            request.setRequestHeader(key, headers[key]);
        });

        request.onreadystatechange = function handleLoad() {
            if (request.readyState !== 4) return;
            if (request.status === 0) return;
            const responseData =
                  request.responseType === "text"
            ? request.responseText
            : request.response;
            if (request.status >= 200 && request.status < 300 || request.status === 304) {
                resolve(responseData);
            } else {
                reject(new Error(`Request failed with status code ${request.status}`));
            }
        };

        request.onerror = function hadleError() {
            reject(new Error("Network Error"));
        };

        request.ontimeout = function handleTimeout() {
            reject(new Error(`Timeout of ${timeout} ms exceeded`));
        };

        request.send(transformData(data));
    });
}

2. 創(chuàng)建 Ajax

有了 xhr ,我們當(dāng)然希望 Ajax 能夠提供一些默認(rèn)配置。這里的 Ajax 函數(shù)不做太過(guò)復(fù)雜的功能,但我們會(huì)簡(jiǎn)單模擬支持默認(rèn) config。

事實(shí)上,最后在 Ajax 中,內(nèi)部調(diào)用的就是 xhr 函數(shù)。類似這個(gè)樣子:

function Ajax(config) {
	// code ...

    return xhr(config);
}

2.1 提供默認(rèn) config

首先,我們來(lái)定義默認(rèn)配置

// 默認(rèn)配置
const defaultconf = {
    method: "get",
    timeout: 500,
    headers: {
        Accept: "application/json, text/plain, */*"
    }
};

// 為 headers 上添加一些方法的默認(rèn) headers, 暫時(shí)掛在 headers[method] 下
["get", "delete", "options", "head"].forEach(method => {
    defaultconf.headers[method] = {};
});

// 為 headers 上添加一些方法的默認(rèn) headers, 暫時(shí)掛在 headers[method]["put", "post", "patch"].forEach(method => {
    defaultconf.headers[method] = {
        "Content-Type": "application/x-www-form-urlencoded"
    };
});

這里我們提供了默認(rèn)的配置,包括默認(rèn)的 method、 timeout、 headers 等,其中,get、 delete、 options、 head 的 headers 默認(rèn)為空;而 put、 post 和 patch 涉及到 data 傳送的會(huì)給一個(gè)默認(rèn)的配置: "Content-Type": "application/x-www-form-urlencoded"。

2.2 合并配置

const method = config.method || defaultconf.method; // 請(qǐng)求的方法名

// 合并 headers
const headers = Object.assign(
    {},
    defaultconf.headers,
    defaultconf[method],
    config.headers || {}
);

// 合并默認(rèn)配置和自定義配置,這里簡(jiǎn)單的進(jìn)行后者對(duì)前者的覆蓋
const conf = Object.assign({}, defaultconf, config);

conf.headers = headers; // 配置的 headers 為我們上面合并好的 headers

// 刪除 conf 配置中,headers 下默認(rèn)的方法的headers塊
["get", "delete", "options", "head", "put", "post", "patch"].forEach(key => {
    delete conf.headers[key];
});

如上所示,我們會(huì)通過(guò)方法名獲取方法名對(duì)應(yīng)的默認(rèn)的 headers,并與傳入配置 headers 和默認(rèn) headers 進(jìn)行合并。然后我們會(huì)合并配置。最后我們不要忘了把合并后的配置中,headers 中方法名對(duì)應(yīng)的配置塊刪除。

2.3 Ajax 函數(shù)

最后,我們會(huì)得到這樣一個(gè) Ajax:

function Ajax(config) {

    const method = config.method || defaultconf.method;

    const headers = Object.assign(
        {},
        defaultconf.headers,
        defaultconf[method],
        config.headers || {}
    );

    const conf = Object.assign({}, defaultconf, config);

    conf.headers = headers;
    ["get", "delete", "options", "head", "put", "post", "patch"].forEach(key => {
        delete conf.headers[key];
    });

    return xhr(conf);
}

3.簡(jiǎn)單的示例

3.1 請(qǐng)求的代碼塊

// 服務(wù)端現(xiàn)有接口,進(jìn)行 post 請(qǐng)求
Ajax({
    method: 'post',
    url: '/simple/post',
    data: {
        a:1,
        b:2
    }
}).then(data => {
    console.log(data)
}).catch(e => {
    console.log('/simple/post', e)
})


// 服務(wù)端暫時(shí)沒(méi)有的接口, 進(jìn)行 post 請(qǐng)求
Ajax({
    method: 'post',
    url: '/test/post',
    data: {
        a:1,
        b:2
    }
}).then(data => {
    console.log(data)
}).catch(e => {
    console.log('/test/post', e)
})

// 服務(wù)端現(xiàn)有接口, 進(jìn)行 get 請(qǐng)求
Ajax({
    url: '/simple/get',
    params: {
        c:1,
        d:2
    }
}).then(data => {
    console.log(data)
}).catch(e => {
    console.log('/simple/get', e)
})

3.2 請(qǐng)求結(jié)果

圖片描述

圖片描述

如圖所示,請(qǐng)求正確接口的 Ajax 請(qǐng)求都得到了正確的返回。而訪問(wèn)服務(wù)端暫時(shí)沒(méi)有的接口則返回了 404 錯(cuò)誤。同時(shí),GET 請(qǐng)求中沒(méi)有顯式提供 method,默認(rèn)配置也能夠及時(shí)生效,默認(rèn)為 GET。

4.小結(jié)

本章節(jié)到此為止,關(guān)于 Ajax 的封裝,核心技術(shù)使用的依然是 XMLHttpRequest 技術(shù)。在自定義 Ajax 中,我們可以提供多種屬性和方法來(lái)豐富和強(qiáng)壯我們的方法,比方說(shuō),我們可以提供 默認(rèn)配置、Promise 語(yǔ)法支持、錯(cuò)誤檢測(cè)及處理、參數(shù)標(biāo)準(zhǔn)化 等等。

本章節(jié)的 Ajax 依然是不完美的,有興趣的同學(xué)可以思考一下還能怎樣去封裝。至少我們還可以提供 request 和 response 的攔截和處理,我們也可以優(yōu)化 config 合并策略。希望這能夠發(fā)動(dòng)同學(xué)們的腦洞風(fēng)暴!