浅谈一下Ajax和fetch的使用

388 阅读3分钟

目标

  • 简单实现一下Ajax
  • 浅谈一下fetch的使用

要点

什么是Ajax

  • 核心就是XMLHTTPRequest

  • AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。

  • AJAX 不是新的编程语言,而是一种使用现有标准的新方法。

  • AJAX 是与服务器交换数据并更新部分网页的艺术,在不重新加载整个页面的情况下。

常见的浏览器请求/响应头/错误码解析

每个参数具体含义童鞋们自行查询😛

常用request header

  • :method: GET
  • :path:
  • :scheme: https
  • accept: application/json, text/plain
  • accept-encoding: gzip, deflate, br
  • cache-control: no-cache
  • cookie: deviceId=c12;
  • origin:
  • referer:
  • user-agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1

常用response header

  • access-control-allow-credentials: true
  • access-control-allow-origin: https://domian/service // 浏览器只允许请求当前域的资源,而对其他域的资源表示不信任
  • content-encoding: gzip
  • content-type: application/json;charset=UTF-8
  • date: Thu, 06 Aug 2020 08:15:05 GMT
  • set-cookie: sess=QvrAQ0Cq+EcDQQPTer2XHlv4fhIRaW/YCb/e4pz/I+uSfZtum4dPp9q4HJL5o+GWuDXHXQLQF2JrIgwzZPaZHWal4qYZy/cfW0Sle/fyB/w=;domain=.yuanfudao.biz;path=/;HttpOnly
  • set-cookie: userid=172270653;domain=.yuanfudao.biz;path=/;HttpOnly
  • status: 200

常用status

  • 200 get 成功
  • 201 post 成功
  • 301 永久重定向
  • 302 临时重定向
  • 304 协商缓存 服务器文件未修改
  • 400 客户端请求有语法错误,不能被服务器识别
  • 403 服务器受到请求,但是拒绝提供服务,可能是跨域
  • 404 请求的资源不存在
  • 405 请求的method不允许
  • 500 服务器发生不可预期的错误

先来一个简单实现

readyState 改变时,就会触发 onreadystatechange 事件
graph TD
xhr.onreadystatechange --> 0:请求未初始化 --> 1:服务器连接已建立 --> 2:请求已接收 --> 3:请求处理中 --> 4:请求已完成且响应已就绪

let xhr = new XMLHttpRequest();
xhr.open('GET', 'http://domain/service');

// request state change event 从 0 到 4 发生变化
xhr.onreadystatechange = function () {
    // request completed?
    if (xhr.readyState !== 4) return;

    if (xhr.status === 200) {
        // request successful - show response
        console.log(xhr.responseText);
    } else {
        // request error
        console.log('HTTP error', xhr.status, xhr.statusText);
    }
};
// xhr.timeout = 3000; // 3 seconds
// xhr.ontimeout = () => console.log('timeout', xhr.responseURL);
// start request
xhr.send();
// 小思考❔ 为什么send方法要放在最后处理

fetch

🌐浏览器新增api,内置了promise,可以通过then方法去处理响应

  • 默认不带cookie
  • 错误不会reject
  • 不支持超时设置
  • 需要借用AbortController中止fetch

不墨迹看例子

fetch('http://domain/service', {
    method: 'GET',
    credentials: 'same-origin' // 控制同源才发送cookie 还有两个值 omit, include
}).then(response => {
    if(response.ok) {
        return response.json()
    }
    throw new Error('http error')
}).then(json => { // 链式调用
    console.log(json)
}).catch(error => {
    console.error(error)
})

// 不支持直接设置超时, 可以用promise实现一下
function fetchTimeout(url, init, timeout = 3000) {
    return new Promise((resolve, reject) => {
        fetch(url, init)
            .then(resolve) // 比如 1000ms后请求响应完成,promise的状态改成fulfilled,3000ms后再去reject也是不会修改最终的结果的,pormise的状态是不可逆的
            .catch(reject);
        setTimeout(reject, timeout);
    })
}

如何中止fetch呢❔


// 中止fetch
const controller = new AbortController();
fetch(
        'http://domain/service', {
            method: 'GET',
            signal: controller.signal
        })
    .then(response => response.json())
    .then(json => console.log(json))
    .catch(error => console.error('Error:', error));

controller.abort();

那fetch是如何实现的呢❓ 使用ts实现一下的 ❗

interface IOptions {
    url: string,
    type?: 'GET' | 'POST',
    data: any,
    timeout?: number
}

// 处理get请求的参数 querystring格式
function formatUrl(object) {
    // a=xxx&b=xxx  querystring
    let dataArr = []
    for(let key in object) {
        dataArr.push(`${key}=${encodeURIComponent(object[key])}`)
    }
    return dataArr.join('&')
}

export function ajax(options: IOptions = {
    url: '',
    type: 'GET',
    data: {},
    timeout: 3000
}) {
    return new Promise((resolve, reject) => {
        // 防止绕过类型检查 ajax({} as any)这样使用
        if (!options.url) {
            return
        }

        const dataToQueryString = formatUrl(options.data)
        let timer = null
        let xhr = null

        // 处理请求超时
        const onStateChange = () => {
            xhr.onreadystatechange = () => {
                if (xhr.readyState === 4) {
                    clearTimeout(timer);
                    if (
                        (xhr.status >= 200 && xhr.status < 300) ||
                        xhr.status === 304
                    ) {
                        resolve(xhr.responseText);
                    } else {
                        reject(xhr.status);
                    }
                }
            };
        };

        if ((window as any).XMLHttpRequest) {
            xhr = new XMLHttpRequest
        } else {
        // 兼容IE
            xhr = new ActiveXObject('Microsoft.XMLHTTP')
        }

        if (options.type.toUpperCase() === 'GET') {
            xhr.open('GET', `${options.url}?${dataToQueryString}`);
            onStateChange();
            xhr.send();
        } else if (options.type.toUpperCase() === 'POST') {
            xhr.open('POST', options.url);
            xhr.setRequestHeader(
                'ContentType',
                'application/x-www-form-urlencoded'
            );
            onStateChange();
            xhr.send(options.data);
        }
        
        // 请求超时处理
        if (options.timeout) {
            timer = setTimeout(() => {
                xhr.abort();
                reject('timeout');
            }, options.timeout);
        }
    })
}

➰欢乐的时光过得特别快,又到时间讲拜拜➰

src=http___p3.ssl.cdn.btime.com_t010248bb6cbd49790c.jpg&refer=http___p3.ssl.cdn.btime.jpg