网络请求的前世今生

303 阅读4分钟

0.0、叠甲

  • 本人水平有限,文章如果有错误希望大家能帮我指出,共同进步
  • 本文只做简单了解,既是写作也是为了加深我自己的记忆

1.0、远古时代的网络请求

  • 用户发起请求,客户端堵塞,必须等到请求响应返回对应的html后,刷新页面

2.0、原生Ajax(XMLHttpRequest)

2.1、什么是ajax请求?

  • AjaxAsynchronous JavaScript + XML,既不需要刷新整个页面得到新的结果
  • XML是一种数据传递格式,现在大多使用json,例如axios默认支持json

2.2、基础的ajax请求

  • Ajax主要是利用浏览器中内置的 XMLHttpRequest

  • 可以看看下面这张图分析一下,XML中的readyState在不同时候分别为几?

  • 带参请求一般是通过拼接url实现的:"url"+params

    var xhr = new XMLHttpRequest();//一个新的 XMLHttpRequest 对象。在调用 send ()向服务器发送请求之前,必须至少调用 open ()来初始化对象。
    console.log("xhr0",xhr);
    //Open ()方法已被调用。在此状态下,可以使用 setRequestHeader ()方法设置请求标头,第三个参数为是否异步请求
    xhr.open('GET','https://www.fastmock.site/mock/59015bc116fee0e86228e4f540fbfcd3/internet/index',true)
    // Send ()已被调用,响应标头已被接收。
    xhr.send()
    // 只要xhr的readystate发生变化就会触发onreadystatechange事件
    xhr.onreadystatechange = function(){
      console.log("xhr",xhr);
      if(xhr.readyState === 4 && xhr.status === 200){
        console.log("xhr",xhr);
      }
    }
    
    image.png
  • xhr的其他实例方法参考: XHR MDN文档

3.0、Jquery封装的ajax(XMLHttpRequest)

3.1、什么是Jquery中的ajax?

  • 封装好的xhr,我们只需要一个$.({})即可,和Jquery操作dom一样简单

3.2、Jquery Ajax的使用

  • 封装了xhr的使用,只需要关心配置

     $.ajax({
        type: 'GET',
        url: "https://www.fastmock.site/mock/59015bc116fee0e86228e4f540fbfcd3/internet/index",
        success: function(res){console.log("res",res)},
        error: function(result) {console.log("err",result)}
     })
     $.ajax({
        type: 'GET',
        url: "https://www.fastmock.site/mock/59015bc116fee0e86228e4f540fbfcd3/internet/indexOOXX",
        success: function(res){console.log("res",res)},
        error: function(result) {console.log("err",result)}
     })
    
    image.png
  • 缺点在于因为jquery的包庞大,并且esmodule时代的来临,让我们越来越不可能为了一个jquery封装的ajax引入整个jquery

  • JqueryAjax其他参数参考:JqueryAjax

4.0、Fetch

  • fetch是一种新的获取接口资源的方式,是为了超越XHR而诞生的新Api

  • fetch优点在于浏览器原生支持,但是也有对老浏览器不兼容的问题

  • fetch是基于promise实现的

      fetch('https://www.fastmock.site/mock/59015bc116fee0e86228e4f540fbfcd3/internet/index')
      .then(response=>{
      //Response 对象并不直接包含实际的 JSON 响应体,而是表示整个 HTTP 响应。
        console.log("response",response)//可以看到fetch返回的是一个stream对象
        return response.json()
      })
      .then(response=>console.log("res",response))
    
    image.png
    // MDN fetch例子
    async function postData(url = '', data = {}) {
      // Default options are marked with *
      const response = await fetch(url, {
        method: 'POST', // *GET, POST, PUT, DELETE, etc.
        mode: 'cors', // no-cors, *cors, same-origin
        cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
        credentials: 'same-origin', // include, *same-origin, omit
        headers: {
          'Content-Type': 'application/json'
          // 'Content-Type': 'application/x-www-form-urlencoded',
        },
        redirect: 'follow', // manual, *follow, error
        referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
        body: JSON.stringify(data) // body data type must match "Content-Type" header
      });
      return response.json(); // parses JSON response into native JavaScript objects
    }
    
    postData('https://example.com/answer', { answer: 42 })
      .then(data => {
        console.log(data); // JSON data parsed by `data.json()` call
      });
    

5.0、Axios(XMLHttpRequest / promise)

5.1、什么是axios

  • Axios 是一个基于 promiseHTTP 库(可以先看8.0),可以用在浏览器node.js
  • node中创建http请求,在浏览器中创建XHR对象
  • 自动转换JSON数据
  • 客户端支持防御XSRF

5.2、axios的使用

  • 来看看官方文档的写法
    //官方写法
    axios({ 
        method: 'post', 
        url: '/user/12345', 
        data: { firstName: 'Fred', lastName: 'Flintstone' } 
    });
    //简写(我还是喜欢简写的):带参get请求
    export const searchFruit = (props:any) => {
          return baseAxios.get('fruit/table/sku/search',
            {
              params:{
                plantCode:props.plantCode,
                searchTxt:props.searchTxt,
                pageSize:props.pageSize,
                pageIndex:props.pageIndex,
              },
            }
      )
    }
    //封装请求域名,便于根据测试/正式快速切换
    const API_PATH = 'https://www.fastmock.site/mock/59015bc116fee0e86228e4f540fbfcd3/internet'
    export const baseAxios = axios.create({
      baseURL: API_PATH
    })
    

5.3、axios响应器/拦截器

  • 响应器和拦截器就像中间件一样,在请求前把需要的token带上,在响应时把错误抛出等等情况
    // 添加请求拦截器
    baseAxios.interceptors.request.use(function (config) {
      // 在发送请求之前做些什么
      const {token} = useTokenState.getState().state;
      config.headers={
        Authorization: "Bearer "+token
      }
      return config;
    }, function (error) {
      // 对请求错误做些什么
      console.log("error",error)
    });
    // 添加响应拦截器
    baseAxios.interceptors.response.use((response)=>{
      // 2xx 范围内的状态码都会触发该函数。
      return response;
    }, function (error) {
      if (error.response.status) {
        //在这里我们return出error的错误,以便于在接口层级拿到报错信息
        switch(error.response.status){
        case 400:
          return Promise.reject(error.response);
        case 404:
          showAlert("404请求的资源无法找到!")
          return Promise.reject(error.response);
        case 500:
          showAlert("服务器错误!")
        return Promise.reject(error.response);
        case 502:
          showAlert("服务器错误!") 
          return Promise.reject(error.response);
        }
        ...
        }
    });
    

6.0、基础小知识:get&post参数位置

  • 一般来说,大家都觉得get请求参数要放在parmas里,post请求参数要放在body
    //以axios简写方式为例
    //get请求参数放在params里,即url里
    axios.get('https://www.fastmock.site/mock/ed4cc5916cf80930a5aa157ffb1686f7/bookstore/books',{
      params:{
        name: "Youngzx",
        id: "NBSP12345678s"
      }
    }).then(res=>{
      console.log("res",res.data)
    })
    //post请求参数放在请求体里
      return baseAxios.post('/pumpkin/mobile/order/update',{
          orderId: orderId,
          type: type,
          item: item
      })
    
  • 实际上放哪里根本没有区别,他们都是通过TCP连接,给Get请求的参数放在Body里,和POSTParams对于后端来说没有区别,只是为了区分不同的请求,而定义的规则,遵守可以让我们的代码看起来更加规范
  • 有的人说GETPOST性能好,因为浏览器只会发送一个数据包,而浏览器发送POST请求会先发起一条option请求,但实际上GET请求会被多次缓存,而POST不会,所以在了解GET与POST本质没有区别的情况下,还是需要合理的运用GET与POST(有的公司只用get,有的只用post)

7.0、基础小知识:formdata 和 json

  • 作为一个小废物,我一开始确实没了解formdata的兼容问题,后面才发现formdata也是有些许兼容问题的(见7.3)
  • 网络请求无非是前端向后端传输对应的数据,后端解析数据,并返回正确的内容给前端
  • 那么数据的格式就显得十分重要,理论上来说formdatajson不过是格式不同,你有能力可以自己造一种更高效的数据传输格式,只要后端能解析即可

7.1、区别

  • formdata一般是通过表单提交,FormData提供了一种表示表单数据的键值对key/value的构造方式
    //formdata在node里没有,可以在浏览器控制台输入,或者import formdata
    var formdata = new FormData();
    formdata.append("username","Youngzx")
    formdata.append("pwd",12345678)
    form.forEach((key, val) => { 
        console.log(key, val) 
        //Youngzx username
        //12345678 pwd
       }
    })
    
  • json是一个序列化的对象数组
  • json 与 js 对象的关系:jsonjs 对象的字符串表示法,它使用文本表示一个 JS 对象的信息,本质是一个字符串
    //js对象
    const obj = {name: 'Youngzx',age:18}
    //json
    const json = '{"name" : "Youngzx" , "age" : "18"}'
    

7.1、json

  • ajax传输json
    let data = { name : "Youngzx", age : 18 }
    console.log(JSON.stringify(data))
    $.ajax({
        url: 'https://www.fastmock.site/mock/ed4cc5916cf80930a5aa157ffb1686f7/bookstore/books',
        dataType: 'json',
        type: 'get',
        //`data`参数需要使用`JSON.stringify()`方法序列化成JSON字符串
        data: JSON.stringify(data),
        //`contentType`需要指定为`'application/json;charset=utf-8'`
        contentType: 'application/json;charset=utf-8',
        success: function (res) {
            console.log(res);
        }
    });
    
  • axios默认支持传输json,不需要我们进行转化

7.2、formdata

  • ajxa传输formdata

  • axios传输formdata(post请求)

    //qs将data转为formdata,在第三个参数中标记请求头为'application/x-www-form-urlencoded'
    export const login = (data:any) => {
      data = qs.stringify(data)
      return baseAxios.post('/auth-center/oauth/token?',data,{headers:
      {
        'content-type': 'application/x-www-form-urlencoded'
      }})
    }
    

7.3、构建formdata的方式

  • 7.1 中的FormData对象,自己append出一个格式正确的formdata
  • 使用qs
    import qs from 'qs'
    const formdata = {
      name: "youngzx",
      age: 18,
      value: 200
    }
    const data = qs.stringify(formdata)//name=youngzx&age=18&value=200
    
  • 表单提交
  • URLSearchParams(有兼容问题,老浏览器不支持)

8.0、基础小知识:Promise

8.1、前情提要

  • 在Js里,所有代码都是单线程执行的
    // 所以我们的网络请求需要异步执行
    function getInfo(){
        console.log("This is the data I want to get after three seconds")
    }
    setTimeout(() => {
        getInfo()
    },3000)
    function getValue(){
        console.log("I want to execute one second after getInfo executes")
    }
    // 如果我想要在getInfo后去执行getValue函数
    setTimeout(() => {
        getInfo()
        setTimeout(() => {
           getValue()
         },1000)
    },3000)
    
  • promise的定义是以后承诺在未来某个时间给我们对应的结果,例如.then以后,async/await以后
  • xhr也是如此,当readystate === 4的时候触发回调函数
  • promise的出现是正是为了解决回调地狱,使得回调的写法变为同步的写法

8.2、promise的使用

  • promise状态:pending(进行中),fullfilled(已成功),rejected(已失败)
  • pormise构造函数接受resolve和reject两个函数作为参数,reslove()会把pending状态转变为fullfilled
    const promise1 = new Promise((resolve, reject) => {
      resolve(3)
    });
    console.log("promise1",promise1)//promise1 Promise { 3 }
    
  • promise的实例可以用.then()中的两个参数指定resolve和reject的回调
    const promise1 = new Promise((resolve, reject) => {
      setTimeout(()=>{
        resolve(777)
      },5000)
    });
    //5秒一到会触发.then的回调,把promise2的pending状态变为fullfilled状态
    promise1.then(resolve=>{
      console.log(resolve)//在5秒钟后得到777
    },reject=>{
      console.log(reject)//reject同理
    })
    //所以我们日常写请求的时候一般是这样得到接口的返回值的,用了async await语法糖会更加简单
    const res = axios.get("urlxxx").then(res=>{return res})
    
  • promise还有许多api例如常用的promise.all,详见:Promise MDN

9.0、基础小知识、语法糖async/await

  • 被async包裹的函数会被当作一个promise
  • await则是.then()得到resolve或者reject以后的内容
  • async/await的出现让代码看起来更像同步代码
    //没有asycn/await的写法
    function getXXX() {
        const p1 = new Promise((resolve,reject) => {
            setTimeout(()=>{
                resolve(300)
            },3000)
        })
        return p1;
    }
    p1.then(res=>{console.log("Res",res)})
    //有async/await的写法
    async function haveAsyncAwait () {
      const res = await getXXX(200)
      console.log(res) // 
    }
    haveAsyncAwait()
    

10.0、总结

  • xhr虽然旧,但是仍然好用,例如axios
  • 原生的xhrjquery封装的xhr过于笨重,耦合过多
  • fetch虽然新,但是需要自己封装的东西还是太多,而且老浏览器不一定适用
  • 现阶段无脑选择axios即可,配合promiseyyds