Ajax&Fetch学习笔记 05、ajax封装(含Promise)

举报
长路 发表于 2022/11/28 21:37:56 2022/11/28
【摘要】 文章目录前言一、内部封装js①utils工具类②Defaults.js(默认属性方法配置类)③constant.js(常量文件)④Ajax.js(自定义Ajax类,用来进行初始化执行一系列操作)二、外部封装(提供给外界使用的API,含测试)2.1、Ajax原生封装2.2、Ajax进阶封装Promise 前言 本篇博客是对于Ajax的封装,若文章中出现相关问题,请指出! 所有博客文件目录索引:博客目

@[toc]

前言

本篇博客是对于Ajax的封装,若文章中出现相关问题,请指出!

所有博客文件目录索引:博客目录索引(持续更新)

一、内部封装js

ajax类
	初始化方法
		绑定响应事件处理
		设置各个header
		发送请求
utils工具类
constant常量类
default配置类:基本的配置属性
index(对外暴露封装类)  给外界调用的接口


①utils工具类

// 添加url请求参数:data为已经序列化的string值,这里只需要判断url后添加?还是&
const addUrlData = (url, data) => {
    if (!data) return '';
    // 用于标识是否包含?
    const mark = url.includes('?') ? '&' : '?';
    return `${mark}${data}`;
}

// 序列化:将传入的对象进行序列化并且进行编码防止有中文文字,如username=changlu&age=18
const serialize = (param) => {
    const results = [];
    for (const [key, value] of Object.entries(param)) {
        results.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
    }
    return results.join('&');
}

// JSON序列化(对象=>JSON字符串,通常放置在请求体中)
const serializeJSON = (data) => {
    return JSON.stringify(data);
}

export { addUrlData, serialize, serializeJSON }


②Defaults.js(默认属性方法配置类)

包含一个xhr对象的相关属性以及方法,目的是方便设置一些默认值:

  • 属性:请求方法、请求头信息、请求体数据、内容类型、响应类型、超时时间、跨域是否携带cookie属性。
  • 方法(监听方法):成功事件、响应码方法、报错方法、停止请求方法、超时方法。
import { HTTP_GET, CONTENT_TYPE_FROM_URLENCODED } from './constant.js'

// 默认配置类
const DEFAULTS = {
    //请求方法(多个,使用常量文件中的):默认GET请求
    method: HTTP_GET,
    // 请求头携带的数据:?username=xx&age=xx
    params: null,
    // 请求体携带的数据:①对象形式。②FormData数据
    data: null,
    // 内容类型(请求头)
    contentType: CONTENT_TYPE_FROM_URLENCODED,
    // 响应类型
    responseType: '',
    // 超时时间
    timeout: 0,
    // 跨域携带cookie属性
    withCredentials: false,

    // 监听事件方法
    success() { },
    httpCodeError() { },//响应码错误
    error() { },
    abort() { },
    timeOut() { }
};

export default DEFAULTS;


③constant.js(常量文件)

目的:将一些常量使用文件管理起来,若是想要修改一个值直接修改配置文件即可,而不是在js中来多次修改常量。

包含如HTTP请求,内容类型以及错误响应码以及描述。

// HTTP请求
export const HTTP_GET = "GET";

// 发送内容类型
export const CONTENT_TYPE_FROM_URLENCODED = 'application/x-www-form-urlencoded';
export const CONTENT_TYPE_FROM_JSON = 'application/json';

// 封装Promise使用
export const ERROR_HTTP_CODE = 1;
export const ERROR_HTTP_CODE_TEXT = 'HTTP状态码异常';
export const ERROR_REQUEST = 2;
export const ERROR_REQUEST_TEXT = '请求被阻止';
export const ERROR_ABORT = 3;
export const ERROR_ABORT_TEXT = '请求停止';
export const ERROR_TIMEOUT = 4;
export const ERROR_TIMEOUT_TEXT = '请求超时';


④Ajax.js(自定义Ajax类,用来进行初始化执行一系列操作)

其中封装了如下的步骤在构造器中

1、构造器传入url、options初始化。

2、监听事件绑定,如success、error、errorhttpcode、abort、timeout等等,方便外部调用者直接外部写回调函数。

3、open()初始化。(请求方法与请求参数是否拼接)

4、特殊属性定制设置,如ResponseType、timeout、WithCredentials。(这部分是将options配置对象中的值真正赋给xhr对象)

5、发送请求send()。考虑多种请求①发送GET请求,其他为POST请求。②data为FormData类型。③data为x-www-form-urlencoded类型传递。④data为JSON类型传递。⑤其他类型传递。

// 导入配置类
import DEFAULTS from './default.js';
// 导入工具类
import { addUrlData, serialize, serializeJSON } from './utils.js';
// 导入常量类
import {
    HTTP_GET,
    CONTENT_TYPE_FROM_URLENCODED,
    CONTENT_TYPE_FROM_JSON
} from "./constant.js";


class Ajax {
    constructor(url, options) {
        this.url = url;
        this.options = Object.assign({}, DEFAULTS, options);

        // 初始化方法
        this.init();
    }

    // 初始化
    init() {
        const xhr = new XMLHttpRequest();

        this.xhr = xhr;

        // 绑定事件
        this.bindEvents();

        xhr.open(this.options.method, this.url + this.addParam(), true);

        // 设置属性到xhr中
        // 设置响应类型
        this.setResponseType();

        // 设置超时时间
        this.setTimeout();

        // 设置跨域是否携带cookie
        this.setWithCredentials();

        // 发送请求
        this.send();


    }

    // 绑定响应事件处理程序
    bindEvents() {
        const xhr = this.xhr;

        const { success, httpCodeError, error, abort, timeOut } = this.options;
        // 绑定监听success事件(通过监听onload事件)
        xhr.addEventListener("load", () => {
            if (this.ok()) {
                success(xhr.response, xhr);
            } else {
                httpCodeError(xhr.response, xhr);
            }
        }, false);

        // error
        xhr.addEventListener("error", () => {
            error(xhr);
        }, false);

        // abort
        xhr.addEventListener("abort", () => {
            abort(xhr);
        }, false);

        // timeout
        xhr.addEventListener("timeout", () => {
            timeOut(xhr);
        }, false);

    }

    // 判断当前状态码是否为成功
    ok() {
        const xhr = this.xhr;
        return (xhr.status >= 200 && xhr.status <= 299) || xhr.status == 304;
    }

    // 在请求地址上添加数据
    addParam() {
        const { params } = this.options;
        //无请求参数
        if (!params) return '';

        // 考虑到传入的url地址是否已经带有?,所以传入URL以及params
        return addUrlData(this.url, serialize(params));

    }

    // 设置响应类型
    setResponseType() {
        this.xhr.responseType = this.options.responseType;
    }

    // 设置超时时间
    setTimeout() {
        const { timeout } = this.options;
        if (timeout > 0) {
            this.xhr.timeout = timeout;
        }
    }

    // 设置是否跨域携带cookie
    setWithCredentials() {
        const { withCredentials } = this.options;
        if (withCredentials) {
            this.xhr.withCredentials = withCredentials;
        }
    }

    // 发送请求
    send() {
        const xhr = this.xhr;

        //测试是否需要使用send()来发送数据
        if (!this.isSendData()) {
            xhr.send(null);
            return;
        }

        // 处理data数据
        let resultData;//最终需要发送的data数据
        const { data } = this.options;

        // 是否为FormData格式数据
        if (this.isFormData(data)) {
            resultData = data;
        } else if (this.isFormURLEncodedData()) {
            // console.log("data=>", data);
            // 发送类型为application/x-www-form-urlencoded数据
            resultData = serialize(data);
            this.xhr.setRequestHeader('Content-Type', CONTENT_TYPE_FROM_URLENCODED);
        } else if (this.isJSONData()) {
            // 发送类型为application/json数据
            resultData = serializeJSON(data);
            this.xhr.setRequestHeader('Content-Type', CONTENT_TYPE_FROM_JSON);
        } else {
            this.setContentType();
            resultData = data;
        }

        xhr.send(resultData);

    }

    // 是否想要使用send()发送数据
    isSendData() {
        // 根据method以及data来确定
        const { method, data } = this.options;
        // data没有值或者请求方法为GET返回false
        if (!data) return false;
        if (method.toLowerCase() === HTTP_GET.toLowerCase())
            return false;
        return true;
    }

    // 是否data值为FormData类
    isFormData() {
        return this.options.data instanceof FormData;
    }

    // 是否发送 application/x-www-form-urlencoded 格式的数据
    isFormURLEncodedData() {
        return this.options.contentType
            .toLowerCase()
            .includes(CONTENT_TYPE_FROM_URLENCODED);
    }

    // 是否发送 application/json 格式的数据
    isJSONData() {
        return this.options.contentType
            .toLowerCase()
            .includes(CONTENT_TYPE_FROM_JSON);
    }

    // 发送其他类型的数据
    setContentType(contentType = this.options.contentType) {
        if (!contentType) return;
        this.xhr.setRequestHeader("Content-Type", contentType);
    }

    // 获取xhr对象
    getXHR() {
        return this.xhr;
    }

}

export default Ajax;


二、外部封装(提供给外界使用的API,含测试)

其实本质就是对Ajax类进行二次封装出API来提供为外界使用。

2.1、Ajax原生封装

如ajax、get、getJSON、post方法,这些名称都十分鲜明对应着不同的请求方式:

import Ajax from './ajax.js'

const ajax = (url, options) => {
    return new Ajax(url, options).getXHR();
}

const get = (url, options) => {
    return ajax(url, {
        ...options, method: 'GET'
    });
}

const getJSON = (url, options) => {
    return ajax(url, {
        ...options, method: 'GET', responseType: 'json'
    });
}

const post = (url, options) => {
    return ajax(url, {
        ...options, method: 'POST'
    });
}

export { ajax, get, getJSON, post }

测试

<script type="module">
    import { ajax, get, getJSON, post } from '../index(原生封装ajax).js'
    const url = 'https://www.imooc.com/api/http/search/suggest?words=js';
    const xhr = ajax(url, {
        method: 'POST',
        data: {
            username: 'changlu',
            age: 18
        },
        timeout: 10,
        responseType: 'json',
        success(response) {
            console.log(response);
        },
        error(xhr) {
            console.log("error");
        },
        abort() {
            console.log("abort");
        },
        timeOut() {
            console.log("timeout");
        }
    });
    // xhr.abort();
</script>


2.2、Ajax进阶封装Promise

相较于原生封装有什么优势呢?

  • 使用原生封装我们需要在传入对象中写一些回调函数如success、error、abort、timeout等等,这样的话也会造成代码量过多的情况。而使用Promise不再需要编写这些额外回调函数,交由Promise,开发者只需要关注then()与catch()方法即可:success=>then,error、abort、timeout=>catch捕获!!!

下面使用Promise进行封装,实际上只需要在Ajax原生封装上进行封装Promise即可:

import Ajax from './ajax.js'
// 引入错误码以及错误描述常量
import {
    ERROR_HTTP_CODE,
    ERROR_REQUEST,
    ERROR_TIMEOUT,
    ERROR_ABORT,
    ERROR_HTTP_CODE_TEXT,
    ERROR_REQUEST_TEXT,
    ERROR_TIMEOUT_TEXT,
    ERROR_ABORT_TEXT
} from './constant.js';


const ajax = (url, options) => {
    let xhr;
    const p = new Promise((resolve, reject) => {
        // 内部对几个方法再次进行封装
        xhr = new Ajax(url, {
            ...options,
            ...{
                //响应成功调用then()方法
                success(response) {
                    resolve(response);
                },
                //其他情况统一传递给catch()捕捉,传递type以及text属性,分为为错误码以及错误描述
                httpCodeError(status) {
                    reject({
                        type: ERROR_HTTP_CODE,
                        text: `${ERROR_HTTP_CODE_TEXT}:${status}`
                    })
                },
                error() {
                    reject({
                        type: ERROR_REQUEST,
                        text: ERROR_REQUEST_TEXT
                    })
                },
                abort() {
                    reject({
                        type: ERROR_ABORT,
                        text: ERROR_ABORT_TEXT
                    })
                },
                timeOut() {
                    reject({
                        type: ERROR_TIMEOUT,
                        text: ERROR_TIMEOUT_TEXT
                    })
                }
            }
        }).getXHR();;
    });
    //将xhr对象赋值给promise对象,为方便开发者可以调用到它自己额外做一些操作等
    p.xhr = xhr;
    p.ERROR_HTTP_CODE = ERROR_HTTP_CODE;
    p.ERROR_REQUEST = ERROR_REQUEST;
    p.ERROR_TIMEOUT = ERROR_TIMEOUT;
    p.ERROR_ABORT = ERROR_ABORT;

    return p;//返回Promise对象
}

const get = (url, options) => {
    return ajax(url, {
        ...options, method: 'GET'
    });
}

const getJSON = (url, options) => {
    return ajax(url, {
        ...options, method: 'GET', responseType: 'json'
    });
}

const post = (url, options) => {
    return ajax(url, {
        ...options, method: 'POST'
    });
}

export { ajax, get, getJSON, post }


测试

<script type="module">
    import { ajax, get, getJSON, post } from '../index(2封装ajax进阶Promise).js';
    const url = 'https://www.imooc.com/api/http/search/suggest?words=js';
    const p = getJSON(url, {
        params: {
            username: 'changlu',
            age: 18
        },
        timeout: 10
    });
    // 获取到xhr
    console.log(p.xhr);
    // 执行abort()来进行测试
    // p.xhr.abort();

    //统一这里来进行结果的处理!!!
    p.then((response) => {
        console.log(response);
    }).catch(error => {
        console.log("type=>" + error.type + ",text=>" + error.text);
    });
</script>
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。