HTTP系列:AJAX基础梳理、axios基本使用梳理

499 阅读10分钟

AJAX核心知识总结

XMLHttpRequest 对象 - JavaScript 教程 - 网道 (wangdoc.com)

2005年2月,AJAX 这个词第一次正式提出,它是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的异步通信,从服务器获取 XML 文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页。后来,AJAX 这个词就成为 JavaScript 脚本发起 HTTP 通信的代名词,也就是说,只要用脚本发起通信,就可以叫做 AJAX 通信。W3C 也在2006年发布了它的国际标准。

AJAX有独特的“异步”特性,也就是说它可以在不重新刷新页面的情况下与服务器通信,交换数据,或更新页面。

使用AJAX的四步过程如下:

let xhr = new XMLHttpRequest;//1
xhr.open( 'GET', './1.json');//2
xhr.onreadystatechange = function () {//3
    if (xhr. readyState === 4 && xhr.status === 200) {
        console. log( xhr.responseText); 
    }
)
xhr.send();//4

分别是:

  • 创建xhr实例
  • 使用 open() 方法指定建立 HTTP 连接的一些细节。在发送请求前作一些配置
  • 指定回调函数,监听通信状态的变化
  • 使用 send() 方法实际发出请求

下面分别详细展开说一下这四步

创建xhr实例

let xhr = new XMLHttpRequest

使用 open() 在发送请求前作一些配置

...
xhr.open( 'GET', './1.json');//2
...

open 方法第一个参数 [method] 是请求方式。包括 GET(get/delete/head/options...) / POST(post/put/patch...)。

GET与POST的区别

GET和POST在官方定义中是没有明确的区别的,但是浏览器或者开发的时候,都有一套约定俗成的规范:

  • GET请求传递给服务器的信息,除了请求头传递以外,要求基于URL问号传参传递给服务器。例如: xhr.open('GET', './1.json?lx=1&name=xxx')
  • POST请求要求传递给服务器的信息,是基于请求主体传递 xhr.send('lx=1&name=xxx')

这两个本质的不同造成了以下的不同:

  1. GET传递的信息不如POST多,因为URL有长度限制(IE->2KB),超过这个长度的信息会被自动截掉,这样导致传递内容过多,最后服务器收到的信息是不完整的。POST理论上是没有限制的,但是传递的东西越多,速度越慢,可能导致浏览器报传输超时的错误,所以实际上我们会自己手动做限制

  2. GET会产生缓存,这个缓存是浏览器默认产生的,不可控的。如果两次及以上,请求相同的API接口,并且传递的参数也一样,浏览器可能会把第一次请求的信息直接返回,而不是从服务器获取最新的信息。

    可以在请求URL的末尾设置随机数,以此来清除GET缓存的副作用: xhr.open('GET', './1.json?lx=1&name=xxx&_'+Math.random())

  3. POST相对于GET来讲更安全一些:GET传递的信息是基于URL末尾拼接,劫持和修改比较容易,而POST请求主体信息的劫持,没有GET容易。

open 方法第二个参数 [url] 是 请求的URL地址,第三个参数 [async] 是否采用异步,默认是 true

监听请求的过程

监听请求的过程,在不同的阶段做不同的处理,包含获取服务器的响应信息,

  • ajax状态 xhr.readyState

    • 0 UNSENT
    • 1 OPENED
    • 2 HEADERS_RECEIVED 响应头信息已经返回
    • 3 LOADING 响应主体信息正在处理
    • 4 DONE 响应主体信息已经返回
  • HTTP状态码 xhr.status / xhr.statusText

    • 200 OK
    • 202 Accepted 服务器已接受请求,但尚未处理(异步)
    • 204 No Content服务器成功处理了请求,但不需要返回任何实体内容
    • 206 Partial Content 服务器已经成功处理了部分 GET 请求(断点续传 Range/If-Range/Content-Range/Content-Type:”multipart/byteranges”/Content-Length…。)
    • 301 Moved Permanently 永久转移 (域名迁移)
    • 302 Move Temporarily 临时转移 (负载均衡)
    • 304 Not Modified
    • 305 Use Proxy
    • 400 Bad Request : 请求参数有误
    • 401 Unauthorized:权限(Authorization)
    • 403 Forbidden 服务器拒绝执行(可能会已响应主体返回)
    • 404 Not Found 地址错误
    • 405 Method Not Allowed 请求方式不被允许
    • 408 Request Timeout 请求超时
    • 500 Internal Server Error 未知服务器错误
    • 503 Service Unavailable 超负荷
    • 505 HTTP Version Not Supported
    • ......
  • 获取响应主体信息

    方法: xhr.response / xhr.responseText / xhr.responseXML

    服务器返回的响应主体信息的格式如下:

    • 字符串。一般是JSON字符串最常用
    • XML格式数据
    • 文件流格式数据(buffer/二进制...)
    • ...
  • 获取响应头信息

    xhr.getResponseHeader / xhr.getAllResponseHeaders image。png

使用 send() 方法发送请求请求

发送请求时,send() 方法中传入的信息,就是请求主体信息

在发送之前,可以设置请求头,告诉后端传送的是什么样的数据格式等信息。请求头必须设置在 xhr.open() 之后, xhr.send() 之前,且不能设置为中文,设置方法如下:

xhr.setRequestHeader('Content-Type', 'application/JSON')

基于请求主体传递给服务器的数据格式是有要求的,几种数据格式分类如下:

  1. form-data格式:主要应用于文件的上传或者表单数据提交

    xhr.setRequestHeader('Content-Type', 'multipart/form-data');
    //手动设置一个表单数据:
    let fd = new FormData;
    fd.append('lx', 0);
    fd.append('name', 'xxx');
    xhr.send(fd);
    

    image.png

  2. x-www-form-urlencoded格式的字符串

    格式为 'lx=1&name=xxx' ,这种方式比较常用。可以安装qs库并使用 Qs.stringify/parse 实现对象和urlencoded格式字符串之间的转换。例如:

    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    xhr.send(Qs.stringify({
      lx: 0,
      name: 'xxx'
    }));
    

    image.png

    image.png

  3. raw格式字符串:

    raw格式分为以下几种

    • 普通字符串 -> text/plain

    • JSON字符串 -> application/json 可以使用 JSON.stringify/parse 对json进行解析,这种方式也常用

    • XML格式字符串 -> application/xml

    • ...

      例如 模拟javascript的raw格式: image。png image。png

    如果什么请求头都不设置的话默认是按照 'text/plain' 类型传送的,并且会把请求体中的内容转化为字符串,例如:

    let xhr = new XMLHttpRequest;
    xhr.open('POST', './data.json');
    xhr.onreadystatechange = function () {
        if (xhr.status=== 200&& xhr.readyState === 4) {
            console.log(xhr.response);
        }
    };
    xhr.send({name:'xxx'});

可以看到发送的对象请求体被自动转化成了字符串 image。png

  1. binary进制数据文件(buffer/二进制等),一般也应用于文件上传

    例如图片 -> image/jpeg,用postman演示如下: image。png

    image。png

  2. GraphQL

开发时在传送不同类型的数据的时候,需要自己设置请求头。和后台协商好需要什么样的格式后,统一请求头。

最后:xhr还有一些其他的方法

image。png 例如 :

  • xhr.abort() 中断请求,执行此方法会触发 xhr.onabort 方法
  • xhr.timeout=0 设置超时时间,超时后可以可以触发 xhr.ontimeout 方法
  • xhr.withCredentials=true 在CORS跨域资源请求中允许携带资源凭证,例如cookie
  • xhr.upload.onprogress 原生监听文件上传的进度的方法

注意:设置请求头信息/超时时间或设置可以携带资源凭证都需要在 xhr.open() 之后, xhr.send() 之前

额外tip:

两种API请求方式

xhr.open('GET', '/userInfo?id=1'); //=>router Query
xhr.open('GET', '/userInfo/1'); //=>router Params

这样请求后端会这样处理: app.get('/userInfo/:id')

表单提交总结

关于表单提交的属性 image.png

axios核心基础总结

官方文档

axios是基于Promise封装的ajax库,核心原理还是XMLHttpRequest

一些属性

axios 本身是函数(对象),有以下常用属性:

  • CancelToken 用于取消ajax请求
  • all 基于 promise.all 实现ajax的并行,当所有的ajax请求都成功,整体才会返回一个成功的promise实例
  • spread 解析出基于 all 返回的结果
  • create 创建一个新的实例,来做单独的全局配置
  • defaults 全局默认配置
  • get / delete / head / options 发送对应方式的请求
  • post / put / patch 发送对应方式的请求
  • request 发送请求
  • interceptors
    • request 请求拦截器
    • response 响应拦截器

一些配置

基于axios发送请求,最后返回的都是promise实例。

一下常用属性以及作用注释如下:

axios({
    // baseURL+url:最终请求的地址
    baseURL: 'http://127.0.0.1:8888',
    url: '/user/list',
    method: 'post',
    // params:基于URL末尾拼接参数的方式,把params对象一项项传递给服务器
    params: {
        lx: 0,
        from: 'wx'
    },
    // data:只针对POST系列请求,设置请求主体传递的信息,默认会把对象变为 application/json 字符串传递给服务器
    data: {
        file: 'xxx',
        size: 1024
    },
    // 在POST请求下,把请求主体信息发送给服务器之前,对请求主体信息进行处理
    transformRequest: function (data) {
        return Qs.stringify(data);
    } 
    // 值:FormData\binary\raw...
    

    
    // 设置请求头信息
    headers: {
        // 所有请求通用,以下两种方法用于设置公共请求头
        'Content-Type': 'multipart/form-data',
        common: {
            'X-Token': 'xxx'
        },
        // 可以只针对某种请求设置
        post: {
            'lx': 1
        },
        get: {
            'lx': 0
        }
    },
    // 零散配置信息
    timeout: 0,
    withCredentials: true,
    // 预设服务器返回的数据格式:不论服务器返回啥格式,内部会转换为我们预设的格式 json/arraybuffer/blob/document/text...
    responseType: 'json',
    // 监听上传/下载进度
    onUploadProgress: function (progressEvent) {},
    onDownloadProgress: function () {},
    // 内部规定,HTTP状态码为多少,算是请求成功,返回成功Promise,否则返回失败的!!
    validateStatus: function (status) {
        return status >= 200 && status < 300;
    }
});

datatransformRequest 细节

默认会把对象变为 application/json 字符串传递给服务器,

...
data: {
    file: 'xxx',
    size: 1024
},
// 在POST请求下,把请求主体信息发送给服务器之前,对请求主体信息进行处理
transformRequest: function (data) {
    return Qs.stringify(data);
} 
...

也可以使用 transformRequest 进行处理,处理完之后,Headers里的Content-Type会自动改变,例如这里就改变成了x-www-form-urlencoded image。png data 也可以直接支持其他格式的数据,例如formData格式的数据

let formData = new FormData();
formData.append('file', 'xxx');
formData.append('size', '1024');
axios({
    ...
    data: formData,//data可以直接支持formData格式的数据
    transformRequest: function (data) {
        if (_.isPlainObject(data)) {
        //这样可以排除formData,new ArrayBuffer()等形式的数据
            // application/json && x-www-form-urlencoded
            return Qs.stringify(data);
        }
        return data;
    },
    ...
})

发送请求的几种方式

  • axios([config])
  • axios.request([config])
  • axios.get/head/delete/options([url],[config]) / axios.post/put/patch([url],[data],[config])

返回结果

返回结果一定是Promise,一些回调函数中返回结果的属性如下:

axios.get('http://127.0.0.1:8888/user/list2', {
    params: {
        lx: 1,
        from: 'wx'
    }
}).then(response => {
    // 服务器返回的状态码和validateStatus指定的匹配条件一致(READY-STATE===4)
    //有以下几个属性:
    // config 设定的配置项
    // headers 响应头信息「对象」
    // request 原生的XHR对象
    // status/statusText 状态码和状态码的描述
    // data 响应主体信息(body)
    console.log('成功', response);
    return response.data;
}).then(data => {
    // 获取响应主体信息,完成对应的业务逻辑
}).catch(reason => {
    //有几种原因
    // 1. 服务器返回的状态码不与validateStatus条件一致「最起码服务器有返回」
    // 2. 压根服务器啥都没返回「例如:断网」
    // 3. 当前请求超时或者被取消
    
    //reason也有几个属性:
//   + config
//   + request
//   + toJSON
//   + message 错误信息
//   + response 如果是网络层失败,是没有response,如果只是axios层失败,是存在response的
//   + isAxiosError 是否为axios层面失败
    console.dir(reason);
});

axios请求失败的几种原因分类(都会走到 catch ):

  1. 网络层失败。请求没有发送成功,或者没有任何的响应(没有完成一个HTTP事务)
  2. axios层面失败
    • 服务器一定有返回,只不过状态码和validateStatus不一致
    • 超时或者取消请求
  3. 业务层失败。一般都是服务器根据业务需求,基于类似于code等标志,来区分不同的业务形态和结果

axios请求举例

POST是基于请求主体把信息传递给服务器,对数据格式有要求,需要在请求头中修改数据格式

请求头: 'Content-Type{MIME}',几个不同type的例子:

  • json格式字符串: type 为'application/json',例如'{"account" :"18310612838"...}',当axios发送post请求并在请求体中传入一个对象时,默认会把这个对象转化为json格式字符串

  • urlencoded格式字符串:'application/x-www-form-urlencoded '例如:'account=xxx&password=xxx '

  • form-data格式,'multipart/ form-data',文件上传就可以使用这种格式,把文件对象(文件流信息)传递给服务器

urlencoded格式上传

axios.post('http://127.0.0.1:9999/user/login', {
    // data
    account: '13711111111',
    password: md5('1234567890')
}, {
    // POST系列请求下:把传递进来的DATA对象,变为自己想要的格式
    transformRequest: (data, headers) => {
        // Qs.stringify/parse实现urlencoded格式字符串和对象之间的转换
        return Qs.stringify(data);
    },
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
    }
}).then(response => {
    console.log(response.data);
})

上传文件

文件上传举例:

//选择一个图片,自动上传
fileInp.onchange = function () {
    let file = fileInp.files[0];
    if (!file) return;

    let fm = new FormData;
    fm.append('file', file);
    axios.post('http://127.0.0.1:9999/upload', fm);
}

发送请求的结果:

image.png

axios发现是form-data格式的数据,会自动将Content-Type改为'multipart/ form-data'