深入JavaScript(8)Ajax、Fetch、Axios详解

86 阅读23分钟

1 浏览器请求

在浏览器端发送⽹络请求的常⻅⽅式:

  1. ajax;

  2. fetch;

  3. axios;

1.1 ajax

Asynchronous JavaScript And XML,翻译过来就是“异步的 Javascript 和 XML” 。

ajax是js异步技术的术语,早起相关的api是XHR。

Ajax 是⼀个技术统称,是⼀个概念模型,它囊括了很多技术,并不特指某⼀技术,它很重要的特性之⼀就是让⻚⾯实现局部刷新。

特点:

  • 局部刷新⻚⾯,⽆需重载整个⻚⾯。

简单来说,Ajax 是⼀种思想, XMLHttpRequest 只是实现 Ajax 的⼀种⽅式。 其中 XMLHttpRequest 模块就是实现 Ajax 的⼀种很好的⽅式。

1.1.1 手写ajax

MDN-XMLHttpRequest

利⽤ XMLHttpRequest模块实现 Ajax。

1.1.1.1 创建异步对象
let xmlHttp;
if (window.XMLHttpRequest) {
  // code for IE7+, Firefox, Chrome, Opera, Safari
  xmlHttp = new XMLHttpRequest();
} else {
  // code for IE6, IE5
  xmlHttp = new ActiveXObject('Microsoft.XMLHTTP');
}

通过 XMLHttpRequest 构造函数创建⼀个异步对象 xmlhttp , IE6, IE5 使⽤ActiveXObject创建,创建的这个异步对象上有很多属性和⽅法,常⽤的有:

  1. onreadystatechange :监听异步对象请求状态码 readyState 的改变,每当 readyState 改变时,就会触发 onreadystatechange事件;

  2. readyState :请求状态码;

    readyState 表示异步对象⽬前的状态,状态码从0到4:

  • 0: 表示请求未初始化,还没有调⽤ open();

  • 1: 服务器连接已建⽴,但是还没有调⽤ send();

  • 2: 请求已接收,正在处理中(通常现在可以从响应中获取内容头);

  • 3: 请求处理中,通常响应中已有部分数据可⽤了,没有全部完成;

  • 4: 当 readyState状态码为4时,表示请求已完成;此阶段确认全部数据都已经解析完毕,可以通过异步对象的属性获取对应数据;

  1. status:http状态码

http状态码表示成功的http状态码有

xmlHttp.status >= 200 && xmlHttp.status < 300 || xmlHttp.status == 304
  1. responseText:后台返回的字符串形式的响应数据;

  2. responseXML:后台返回的XML形式的响应数据;

1.1.1.2 设置请求⽅式和请求地址

创建异步对象之后,通过 open() ⽅法设置ajax请求⽅式和请求地址 格式:

xmlHttp.open("GET/POST","ajax-get.txt",true)

第⼀个参数:请求的类型;GET 还是 POST;

第⼆个参数:表示请求的⽂件的地址url;

第三个参数:设置请求⽅法是不是异步async,true为异步, false为同步。AJAX存在的意义就是发异步请求,所以第三个参数永远传true;

注意:有个问题,就是IE中的缓存问题:

在IE浏览器中,如果通过Ajax发送GET请求,那么IE浏览器认为,同⼀个URL只有⼀个结果,如果地址没有发⽣变化,它就会把上⼀次返回的结果,直接返回。这样我们不能实时的拿到变化后的数据。如果要想我们拿到实时数据,必须保证每次的URL都是不⼀样的,有两种⽅式:

  1. Math.random();

  2. new Date().getTime();

即在请求地址后⾯拼接上?t=随机数或者1970.01.01⾄当前的毫秒数 所以在IE中通过ajax发送get请求时,可以设置请求地址为:

xmlHttp.open("GET","ajax-get.txt?t=" + (new Date().getTime()),true);
//或
xmlHttp.open("GET","ajax-get.txt?t=" + Math.random(),true);
1.1.1.3 发送请求

直接通过异步对象的send()发送请求

xmlHttp.send();

特别注意的是: 如果发送POST请求,使⽤ setRequestHeader() 来添加 HTTP请求头,并在send()⽅法中传递要发送的数据:

xmlHttp.open("POST","ajax_test.html",true);
xmlHttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xmlHttp.send("fname=Henry&lname=Ford");
1.1.1.4 通过onreadystatechange监听状态变化

当异步对象的readyState发⽣改变,会触发 onreadystatechange函数,当readyState变成为4时,表示当前状态是请求完毕的状态,同时当http的响应码status为200到300之间(包括200和300)或为304时,表示ajax请求成功;当http状态码不是200到300之间的数也不是304时,表示请求不成功

//4.监听状态变化
xmlHttp.onreadystatechange = () => {
  // 判断当前状态改变是请求完毕的状态吗
  if (xmlHttp.readyState === 4) {
    if (xmlHttp.status >= 200 && xmlHttp.status < 300 || xmlHttp.status ==
      304) {
      console.log("成功的接收到服务器返回的数据");
    } else {
      console.log("不成功!");
    }
  }
}
1.1.1.5 处理返回的结果

如果成功,可通过异步对象的 responseText 属性来获取服务器返回的字符串

接下来,我们来封装⼀个⽅法ajax()⽤于发送请求

封装的时候,需要注意:

  1. URL当中只能出现字⺟ 数字 下划线和ASCII码,不能出现中⽂,可以使⽤encodeURIComponent()转码;

  2. 当我们利⽤我们的ajax放的发送⼀个请求到远处服务器时,我们需要等待远程服务器去响应我们的请求,等待远程服务器将响应的结果返回给我们,但是这个响应的速度是不确定的,因为响应的速度是由本地⽹络和远程服务器的⽹速等共同决定的,所以我们不可能⼀直等待服务器的响应。这⾥需要设置超时时间;

ajax({
  type: 'GET',
  url: 'http://localhost:3000/posts',
  timeout: 1000,
  success: data => {
    console.log('success', data);
  },
  error: err => {
    console.log('error', err);
  },
});
1.1.1.6 其他

⾄此,jQuery官⽅的ajax还是有⼀定的差异,所以还需要进⼀步完善

  1. 传递多个参数,需要保持传递顺序。解决⽅案是可以改写成传递的是⼀个对象;因为对象⾥⾯的值,传递的是⼀个对象就不⽤考虑先后顺序,⾥⾯⽤的参数通过对象名.属性名的形式获取;

  2. 传递请求类型的区分⼤⼩写,jQuery官⽅的是⼤⼩写都可以;解决⽅案是可以使⽤ toLowerCase ()或者 toUpperCase ()将类型转成⼤写或⼩写再对⽐;

  3. 我们传递的数据⽤的名字是obj,jQuery官⽅⽤的是data;

const ajax = option => {
  //type, url, data, timeout, success, error将所有参数换成一个对象{}

  //  0.将对象转换成字符串

  //处理obj
  const objToString = data => {
    data.t = new Date().getTime();
    let res = [];
    for (let key in data) {
      //需要将key和value转成非中文的形式,因为url不能有中文。使用encodeURIComponent();
      res.push(encodeURIComponent(key) + ' = ' + encodeURIComponent(data[key]));
    }
    return res.join('&');
  };

  let str = objToString(option.data || {});

  //  1.创建一个异步对象xmlHttp;
  var xmlHttp, timer;
  if (window.XMLHttpRequest) {
    xmlHttp = new XMLHttpRequest();
  } else {
    // code for IE6, IE5
    xmlHttp = new ActiveXObject('Microsoft.xmlHttp');
  }

  //  2.设置请求方式和请求地址;
  // 判断请求的类型是POST还是GET
  if (option.type.toLowerCase() === 'get') {
    xmlHttp.open(option.type, option.url + '?' + str, true);
    //  3.发送请求;
    xmlHttp.send();
  } else if (option.type.toLowerCase() === 'post') {
    xmlHttp.open(option.type, option.url, true);
    // 注意:在post请求中,必须在open和send之间添加HTTP请求头:setRequestHeader(header,value);
    xmlHttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    //  3.发送请求;
    xmlHttp.send(str);
  }

  //  4.监听状态的变化;
  xmlHttp.onreadystatechange = function () {
    clearInterval(timer);
    if (xmlHttp.readyState === 4) {
      if ((xmlHttp.status >= 200 && xmlHttp.status < 300) || xmlHttp.status == 304) {
        //  5.处理返回的结果;
        option.success(xmlHttp.responseText); //成功后回调;
      } else {
        option.error(xmlHttp.responseText); //失败后回调;
      }
    }
  };

  //判断外界是否传入了超时时间
  if (option.timeout) {
    timer = setInterval(function () {
      xmlHttp.abort(); //中断请求
      clearInterval(timer);
    }, option.timeout);
  }
};

1.1.2 测试

选择 json-server 启动本地服务,mock数据:

// data/data.json
{
  "posts": [
    {
      "id": 1,
      "title": "json-server",
      "author": "xianzao"
    }
  ],
  "comments": [
    {
      "id": 1,
      "body": "some comment",
      "postId": 1
    }
  ],
  "profile": {
    "name": "xianzao"
  }
}

终端执行 json-server data/data.json 启动本地服务(服务起好后,能通过链接获取上面的json数据)

执行代码

ajax({
  type: 'GET',
  url: 'http://localhost:3000/posts',
  data: {
    a: 1
  },
  timeout: 1000,
  success: data => {
    console.log('success', data);
  },
  error: err => {
    console.log('error', err);
  },
});

image.png

1.1.3 总结

我们可以发现,ajax只是⼀种异步请求的⽅式,并不特指某⼀种具体的实现⽅式,但随着使⽤这种⽅式实现⽹络请求时内部⼜包含请求的情况,就会出现回调地狱,这也是XHR的诟病之⼀,因此,后来才催⽣了更加优雅的请求⽅式。

1.2 fetch

Fetch 是在 ES6 出现的,它使⽤了 ES6 提出的 Promise 对象。它是 XMLHttpRequest的替代品。

有⼈把它与 Ajax 作⽐较,其实这是不对的,我们通常所说的 Ajax 是指使⽤ XMLHttpRequest实现的 Ajax,所以真正应该和 XMLHttpRequest 作⽐较。

Fetch 是⼀个 API,它是真实存在的,它是基于 Promise 的。

建议阅读:MDN

以下内容摘选⾃:fetch教学

fetch()的功能与 XMLHttpRequest 基本相同,但有三个差异:

  1. fetch使⽤ Promise,不使⽤回调函数,因此⼤⼤简化了写法,写起来更简洁;
  2. fetch采⽤模块化设计,API 分散在多个对象上(Response 对象、Request 对象、Headers 对象),更合理⼀些;相⽐之下, XMLHttpRequest 的 API 设计并不是很好,输⼊、输出、状态都在同⼀个接⼝管理,容易写出⾮常混乱的代码;
  3. fetch通过数据流(Stream 对象)处理数据,可以分块读取,有利于提⾼⽹站性能表现,减少内存占⽤,对于请求⼤⽂件或者⽹速慢的场景相当有⽤。 XMLHTTPRequest 对象不⽀持数据流,所有的数据必须放在缓存⾥,不⽀持分块读取,必须等待全部拿到后,再⼀次性吐出来;

在⽤法上,fetch()接受⼀个 URL 字符串作为参数,默认向该⽹址发出 GET 请求,返回⼀个 Promise 对象。它的基本⽤法如下。

fetch(url)
    .then(...)
    .catch(...)

下⾯是⼀个demo,从服务器获取 JSON 数据。

fetch('http://localhost:3000/posts')
  .then(res => res.json())
  .then(data => {
    console.log(data);
  })
  .catch(err => {
    console.log(err);
  });

image.png

fetch()接收到的res是⼀个 Stream 对象, res.json()是⼀个异步操作,取出所有内容,并将其转为 JSON 对象。

Promise 可以使⽤ await 语法改写,使得语义更清晰。

async function getJSON() {
  let url = 'http://localhost:3000/posts';
  try {
    let response = await fetch(url);
    return await response.json();
  } catch (error) {
    console.log('Request Failed', error);
  }
}

上⾯示例中,await语句必须放在 try...catch⾥⾯,这样才能捕捉异步操作中可能发⽣的错误。

1.2.1 Response

  1. Response 对象的同步属性

fetch()请求成功以后,得到的是⼀个 Response 对象。它对应服务器的 HTTP 回应。

Response 包含的数据通过 Stream 接⼝异步读取,但是它还包含⼀些同步属性,对应 HTTP 回应的标头信息(Headers),可以⽴即读取。

image.png

response.statusresponse.statusText 就是 Response 的同步属性,可以⽴即读取。

1.2.1.1 标头信息
  1. Response.ok

Response.ok 属性返回⼀个布尔值,表示请求是否成功,true对应 HTTP 请求的状态码 200 到299,false对应其他的状态码;

  1. Response.status

Response.status 属性返回⼀个数字,表示 HTTP 回应的状态码(例如200,表示成功请求);

  1. Response.statusText

Response.statusText 属性返回⼀个字符串,表示 HTTP 回应的状态信息(例如请求成功以后,服务器返回"OK");

  1. Response.url

Response.url 属性返回请求的 URL。如果 URL 存在跳转,该属性返回的是最终 URL;

  1. Response.type

Response.type 属性返回请求的类型。可能的值如下:

  • basic:普通请求,即同源请求;

  • cors:跨域请求;

  • error:⽹络错误,主要⽤于 Service Worker;

  • opaque:如果fetch()请求的type属性设为no-cors,就会返回这个值。表示发出的是简单的跨域请求,类似< form >表单的那种跨域请求;

  • opaqueredirect:如果fetch()请求的redirect属性设为manual,就会返回这个值;

  1. Response.redirected

Response.redirected 属性返回⼀个布尔值,表示请求是否发⽣过跳转。

1.2.1.2 判断请求是否成功

fetch()发出请求以后,有⼀个很重要的注意点:只有⽹络错误,或者⽆法连接时,fetch()才会报错,其他情况都不会报错,⽽是认为请求成功。这就是说,即使服务器返回的状态码是 4xx 或 5xx,fetch()也不会报错(即 Promise 不会变为 rejected状态)。

  1. Response.status

Response.status 属性,得到 HTTP 回应的真实状态码,才能判断请求是否成功。

async function fetchText(URL) {
  let response = await fetch(URL);
  if (response.status >= 200 && response.status < 300) {
    return await response.text();
  } else {
    throw new Error(response.statusText);
  }
}

response.status 属性只有等于 2xx (200~299),才能认定请求成功。这⾥不⽤考虑⽹址跳转(状态码为 3xx),因为fetch()会将跳转的状态码⾃动转为 200。

  1. response.ok是否为true
if (response.ok) {
  // 请求成功
} else {
  // 请求失败
}
1.2.1.3 Response.headers

Response 对象还有⼀个Response.headers属性,指向⼀个 Headers 对象,对应 HTTP 回应的所有标头。

Headers 对象可以使⽤for...of循环进⾏遍历。

const response = await fetch(url);
for (let [key, value] of response.headers) {
  console.log(`${key} : ${value}`);
}
// 或者
for (let [key, value] of response.headers.entries()) {
  console.log(`${key} : ${value}`);
}

Headers 对象提供了以下⽅法,⽤来操作标头。

Headers.get():根据指定的键名,返回键值。

Headers.has(): 返回⼀个布尔值,表示是否包含某个标头。

Headers.set():将指定的键名设置为新的键值,如果该键名不存在则会添加。

Headers.append():添加标头。

Headers.delete():删除标头。

Headers.keys():返回⼀个遍历器,可以依次遍历所有键名。

Headers.values():返回⼀个遍历器,可以依次遍历所有键值。

Headers.entries():返回⼀个遍历器,可以依次遍历所有键值对([key, value])。

Headers.forEach():依次遍历标头,每个标头都会执⾏⼀次参数函数。

这些⽅法中,最常⽤的是 response.headers.get() ,⽤于读取某个标头的值。

let response = await fetch(url);
response.headers.get('Content-Type')
// application/json; charset=utf-8

Headers.keys() 和 Headers.values()⽅法⽤来分别遍历标头的键名和键值。

// 键名
for (let key of myHeaders.keys()) {
  console.log(key);
}
// 键值
for (let value of myHeaders.values()) {
  console.log(value);
}

Headers.forEach()⽅法也可以遍历所有的键值和键名。

let response = await fetch(url);
response.headers.forEach(
  (value, key) => console.log(key, ':', value)
);
1.2.1.4 读取内容的方法

Response对象根据服务器返回的不同类型的数据,提供了不同的读取⽅法。

  • response.text():得到⽂本字符串;

  • response.json():得到 JSON 对象;

  • response.blob():得到⼆进制 Blob 对象;

  • response.formData():得到 FormData 表单对象;

  • response.arrayBuffer():得到⼆进制 ArrayBuffer 对象;

这5个读取⽅法都是异步的,返回的都是 Promise 对象。必须等到异步操作结束,才能得到服务器返回的完整数据。

  1. response.text()

response.text() 可以⽤于获取⽂本数据,⽐如 HTML ⽂件。

const response = await fetch('/users.html');
const body = await response.text();
document.body.innerHTML = body
  1. response.json()

response.json() 主要⽤于获取服务器返回的 JSON 数据。

  1. response.formData()

response.formData() 主要⽤在 Service Worker ⾥⾯,拦截⽤户提交的表单,修改某些数据以后,再提交给服务器。

  1. response.blob()

response.blob() ⽤于获取⼆进制⽂件。

const response = await fetch('flower.jpg');
const myBlob = await response.blob();
const objectURL = URL.createObjectURL(myBlob);
const myImage = document.querySelector('img');
myImage.src = objectURL;

上⾯示例读取图⽚⽂件flower.jpg,显示在⽹⻚上。

  1. response.arrayBuffer()

response.arrayBuffer() 主要⽤于获取流媒体⽂件。

const audioCtx = new window.AudioContext();
const source = audioCtx.createBufferSource();

const response = await fetch('song.ogg');
const buffer = await response.arrayBuffer();

const decodeData = await audioCtx.decodeAudioData(buffer);
source.buffer = buffer;
source.connect(audioCtx.destination);
source.loop = true;

上⾯示例是response.arrayBuffer()获取⾳频⽂件song.ogg,然后在线播放的例⼦。

1.2.1.5 Response.clone

Stream 对象只能读取⼀次,读取完就没了。这意味着,前⼀节的五个读取⽅法,只能使⽤⼀个,否则会报错。

let text = await response.text();
let json = await response.json(); // 报错

上⾯示例先使⽤了 response.text() ,就把 Stream 读完了。后⾯再调⽤ response.json(),就没有内容可读了,所以报错。

Response 对象提供 Response.clone() ⽅法,创建Response对象的副本,实现多次读取。

const response1 = await fetch('flowers.jpg');
const response2 = response1.clone();

const myBlob1 = await response1.blob();
const myBlob2 = await response2.blob();

image1.src = URL.createObjectURL(myBlob1);
image2.src = URL.createObjectURL(myBlob2);

上⾯示例中, response.clone() 复制了⼀份 Response 对象,然后将同⼀张图⽚读取了两次。

1.2.1.6. Response.body

Response.body 属性是 Response 对象暴露出的底层接⼝,返回⼀个 ReadableStream 对象,供⽤户操作。

它可以⽤来分块读取内容,应⽤之⼀就是显示下载的进度。

const response = await fetch('flower.jpg');
const reader = response.body.getReader();
while (true) {
  const { done, value } = await reader.read();
  if (done) {
    break;
  }
  console.log(`Received ${value.length} bytes`)
}

response.body.getReader() ⽅法返回⼀个遍历器。这个遍历器的read()⽅法每次返回⼀个对象,表示本次读取的内容块。 这个对象的done属性是⼀个布尔值,⽤来判断有没有读完;value属性是⼀个 arrayBuffer 数组,表示内容块的内容,⽽value.length属性是当前块的⼤⼩。

1.2.2 定制HTTTP请求

fetch()的第⼀个参数是 URL,还可以接受第⼆个参数,作为配置对象,定制发出的 HTTP 请求

fetch(url, optionObj)

HTTP 请求的⽅法、标头、数据体都在这个对象⾥⾯设置。

1.2.2.1 POST请求
const response = await fetch(url, {
  method: 'POST',
  headers: {
    "Content-type": "application/x-www-form-urlencoded; charset=UTF-8",
  },
  body: 'foo=bar&lorem=ipsum',
});
const json = await response.json();

配置对象⽤到了三个属性。

method:HTTP 请求的⽅法,POSTDELETEPUT都在这个属性设置。

headers:⼀个对象,⽤来定制 HTTP 请求的标头。

body:POST 请求的数据体。

注意,有些标头不能通过headers属性设置,⽐如 Content-Length 、Cookie、Host等等。它们是由浏览器⾃动⽣成,⽆法修改。

1.2.2.2. 提交 JSON 数据
const user = { name: 'John', surname: 'Smith' };
const response = await fetch('/article/fetch/post/user', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json;charset=utf-8'
  },
  body: JSON.stringify(user)
});

标头 Content-Type 要设成 'application/json;charset=utf-8'。因为默认发送的是纯⽂本, Content-Type 的默认值是 'text/plain;charset=UTF-8'。

1.2.2.3 提交表单
const form = document.querySelector('form');
const response = await fetch('/users', {
  method: 'POST',
  body: new FormData(form)
})
1.2.2.4. ⽂件上传

如果表单⾥⾯有⽂件选择器,可以⽤前⼀个例⼦的写法,上传的⽂件包含在整个表单⾥⾯,⼀起提交。

另⼀种⽅法是⽤脚本添加⽂件,构造出⼀个表单,进⾏上传,请看下⾯的例⼦。

const input = document.querySelector('input[type="file"]');

const data = new FormData();
data.append('file', input.files[0]);
data.append('user', 'foo');

fetch('/avatars', {
  method: 'POST',
  body: data
});
1.2.2.5. 直接上传⼆进制数据

fetch()也可以直接上传⼆进制数据,将 Blob 或 arrayBuffer 数据放在body属性⾥⾯。

let blob = await new Promise(resolve =>
  canvasElem.toBlob(resolve, 'image/png')
);
let response = await fetch('/article/fetch/post/image', {
  method: 'POST',
  body: blob
});

1.2.3. option API

fetch()第⼆个参数的完整 API 如下:

const response = fetch(url, {
  method: "GET",
  headers: {
    "Content-Type": "text/plain;charset=UTF-8"
  },
  body: undefined,
  referrer: "about:client",
  referrerPolicy: "no-referrer-when-downgrade",
  mode: "cors",
  credentials: "same-origin",
  cache: "default",
  redirect: "follow",
  integrity: "",
  keepalive: false,
  signal: undefined
});

fetch()请求的底层⽤的是 Request() 对象 的接⼝,参数完全⼀样,因此上⾯的 API 也是Request()的API。

1.2.3.1. cache

cache属性指定如何处理缓存。可能的取值如下:

  • default:默认值,先在缓存⾥⾯寻找匹配的请求;

  • no-store:直接请求远程服务器,并且不更新缓存;

  • reload:直接请求远程服务器,并且更新缓存;

  • no-cache:将服务器资源跟本地缓存进⾏⽐较,有新的版本才使⽤服务器资源,否则使⽤缓存;

  • force-cache:缓存优先,只有不存在缓存的情况下,才请求远程服务器;

  • only-if-cached:只检查缓存,如果缓存⾥⾯不存在,将返回504错误;

1.2.3.2. mode

mode属性指定请求的模式。可能的取值如下:

  • cors:默认值,允许跨域请求;

  • same-origin:只允许同源请求;

  • no-cors:请求⽅法只限于 GET、POST 和 HEAD,并且只能使⽤有限的⼏个简单标头,不能添加跨域的复杂标头,相当于提交表单所能发出的请求;

1.2.3.3. credentials

credentials属性指定是否发送 Cookie。可能的取值如下:

  • same-origin:默认值,同源请求时发送 Cookie,跨域请求时不发送;

  • include:不管同源请求,还是跨域请求,⼀律发送 Cookie;

  • omit:⼀律不发送;

跨域请求发送 Cookie,需要将credentials属性设为include。

1.2.3.4. signal

signal属性指定⼀个 AbortSignal 实例,⽤于取消fetch()请求。

1.2.3.5. keepalive

keepalive属性⽤于⻚⾯卸载时,告诉浏览器在后台保持连接,继续发送数据。

⼀个典型的场景就是,⽤户离开⽹⻚时,脚本向服务器提交⼀些⽤户⾏为的统计信息。这时,如果不⽤keepalive属性,数据可能⽆法发送,因为浏览器已经把⻚⾯卸载了。

window.onunload = function () {
  fetch('/analytics', {
    method: 'POST',
    body: "statistics",
    keepalive: true
  });
};
1.2.3.6. redirect

redirect属性指定 HTTP 跳转的处理⽅法。可能的取值如下:

  • follow:默认值,fetch()跟随 HTTP 跳转;

  • error:如果发⽣跳转,fetch()就报错;

  • manual:fetch()不跟随 HTTP 跳转,但是response.url属性会指向新的 URL,response.redirected属性会变为true,由开发者⾃⼰决定后续如何处理跳转;

1.2.3.7. integrity

integrity属性指定⼀个哈希值,⽤于检查 HTTP 回应传回的数据是否等于这个预先设定的哈希值。

⽐如,下载⽂件时,检查⽂件的 SHA-256 哈希值是否相符,确保没有被篡改。

fetch('http://site.com/file', {
    integrity: 'sha256-abcdef'
});
1.2.3.8. referrer

referrer属性⽤于设定fetch()请求的referer标头。

这个属性可以为任意字符串,也可以设为空字符串(即不发送referer标头)。

1.2.3.9. referrerPolicy

referrerPolicy属性⽤于设定Referer标头的规则。可能的取值如下:

no-referrer-when-downgrade :默认值,总是发送Referer标头,除⾮从 HTTPS ⻚⾯请求HTTP 资源时不发送;

no-referrer :不发送Referer标头;

origin :Referer标头只包含域名,不包含完整的路径;

origin-when-cross-origin :同源请求Referer标头包含完整的路径,跨域请求只包含域名;

same-origin :跨域请求不发送Referer,同源请求发送;

strict-origin :Referer标头只包含域名,HTTPS ⻚⾯请求 HTTP 资源时不发送Referer标头;

strict-origin-when-cross-origin :同源请求时Referer标头包含完整路径,跨域请求时只包含域名,HTTPS ⻚⾯请求 HTTP 资源时不发送该标头;

unsafe-url :不管什么情况,总是发送Referer标头;

1.2.4. fetch cancel

fetch()请求发送以后,如果中途想要取消,需要使⽤ AbortController 对象。

let controller = new AbortController();
let signal = controller.signal;

fetch(url, {
  signal: controller.signal
});

signal.addEventListener('abort',
  () => console.log('abort!')
);

controller.abort(); // 取消

console.log(signal.aborted); // true

上⾯示例中,⾸先新建 AbortController 实例,然后发送fetch()请求,配置对象的signal属性必须指定接收 AbortController 实例发送的信号 controller.signal 。

controller.abort() ⽅法⽤于发出取消信号。这时会触发abort事件,这个事件可以监听,也可以通过 controller.signal.aborted 属性判断取消信号是否已经发出。

下⾯是⼀个1秒后⾃动取消请求的例⼦。

let controller = new AbortController();
setTimeout(() => controller.abort(), 1000);
try {
  let response = await fetch('/long-operation', {
    signal: controller.signal
  });
} catch (err) {
  if (err.name == 'AbortError') {
    console.log('Aborted!');
  } else {
    throw err;
  }
}

1.2.5. 测试

请求使用 json-server 启动的服务的数据,只需要执⾏:

fetch('http://localhost:3000/posts')
  .then(res => res.json())
  .then(data => {
    console.log(data);
  })
  .catch(err => {
    console.log(err);
  });

有兴趣的可以参考:fetch polyfill

1.3 axios

官网地址

axios是⼀个⽤于⽹络请求的第三⽅库,是⼀个基于Promise ⽤于浏览器和 nodejs 的 HTTP 客户端,它本身具有以下特征:

  • 从浏览器中创建 XMLHttpRequest;

  • 从 node.js 发出 http 请求;

  • ⽀持 Promise API;

  • 拦截请求和响应;

  • 转换请求和响应数据;

  • 取消请求;

  • ⾃动转换JSON数据;

  • 客户端⽀持防⽌CSRF/XSRF;

1.3.1 基础使用

Axios 提供了两种不同的形式来发送 HTTP 请求:

1.3.1.1 方法

axios(config) ⽅法接收⼀个对象,这个对象包含了⼀些对请求的配置, axios 会根据这些配置来发送对应的 HTTP 请求

最基本的配置项应该包括:

  1. method 请求的⽅法(可选值: get , post 等);

  2. url 请求的地址 (必须项);

  3. data 请求发送的数据(post等请求需要);

默认的请求⽅法是get所以如果是get请求可以不设置method

// 发送POST请求
axios({
  method: 'post',
  url: 'http://localhost:3000/posts',
  data: {
    firstName: 'Fred',
    lastName: 'Flintstone'
  }
});

请求响应的处理在 then 和 catch 回调中,请求正常会进⼊ then ,请求异常则会进 catch

// 发送POST请求
axios({
  method: 'post',
  url: 'http://localhost:3000/posts',
  data: {
    firstName: 'Fred',
    lastName: 'Flintstone'
  }
}).then(res => {
  console.log(res)
}).catch(err => {
  console.log(err)
})


// 发送 GET 请求(默认的⽅法)
axios('http://localhost:3000/posts');
1.3.1.2 请求别名
// 发送GET请求
axios.get('http://localhost:3000/posts?ID=123').then(function (response) {
  console.log(response);
}).catch(function (error) {
  console.log(error);
});

// 发送POST请求
axios.post('http://localhost:3000/posts', {
  firstName: 'Fred',
  lastName: 'Flintstone'
}).then(function (response) {
  console.log('response', response);
}).catch(function (error) {
  console.log(error);
});

1.3.2 响应数据

其中的 data 是后端返回的数据,⼀般只需要关注 response 中的 data 字段就⾏

{
  // `data` 由服务器提供的响应
  data: { },
  // `status` 来⾃服务器响应的 HTTP 状态码
  status: 200,
  // `statusText` 来⾃服务器响应的 HTTP 状态信息
  statusText: 'OK',
  // `headers` 服务器响应的头
  headers: { },
  // `config` 是为请求提供的配置信息
  config: { },
  // 'request'
  // `request` is the request that generated this response
  // It is the last ClientRequest instance in node.js (in redirects)
  // and an XMLHttpRequest instance the browser
  request: { }
}

1.3.3 创建实例

可以使⽤⾃定义配置新建⼀个 axios 实例 axios.create([config]):

const instance = axios.create({
    baseURL: 'https://some-domain.com/api/',
    timeout: 1000,
    headers: {'X-Custom-Header': 'foobar'}
});

创建的实例中的 axios() api 改为了 request() api,使⽤⽅式是⼀样的,其他如请求别名等函数,都没有改变

以下是实例所拥有的⽅法

  • request(config);

  • get(url[, config]);

  • delete(url[, config]);

  • head(url[, config]);

  • options(url[, config]);

  • post(url[, data[, config]]);

  • put(url[, data[, config]]);

  • patch(url[, data[, config]]);

axios会把这些 ⽅法中的config 会和创建实例时指定的 config 合并到⼀起使⽤

1.3.4 拦截器

axios.interceptors.request 请求拦截器

axios.interceptors.response 响应拦截器

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
  // 在发送请求之前做些什么
  config.header["Token"] = "xxxx"
  return config;
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error);
});

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
  // 对响应数据做点什么
  if (response.status === 200) {
    return response.data
  } else {
    return Promise.reject(new Error('error'))
  }
}, function (error) {
  // 对响应错误做点什么
  return Promise.reject(error);
});

如果想要取消拦截器,可以通过使⽤⼀个变量来接收设置拦截器时返回的实例,然后使⽤ eject 来取消拦截器

const myInterceptor = axios.interceptors.request.use(function () {/*...*/});

axios.interceptors.request.eject(myInterceptor);

1.3.5 完整的请求配置

{
  // `url` 是⽤于请求的服务器 URL
  url: '/user',
  // `method` 是创建请求时使⽤的⽅法
  method: 'get', // default
  // `baseURL` 将⾃动加在 `url` 前⾯,除⾮ `url` 是⼀个绝对 URL。
  // 它可以通过设置⼀个 `baseURL` 便于为 axios 实例的⽅法传递相对 URL
  baseURL: 'https://some-domain.com/api/',
  // `transformRequest` 允许在向服务器发送前,修改请求数据
  // 只能⽤在 'PUT', 'POST' 和 'PATCH' 这⼏个请求⽅法
  // 后⾯数组中的函数必须返回⼀个字符串,或 ArrayBuffer,或 Stream
  transformRequest: [function (data, headers) {
    // 对 data 进⾏任意转换处理
    return data;
  }],
  // `transformResponse` 在传递给 then/catch 前,允许修改响应数据
  transformResponse: [function (data) {
    // 对 data 进⾏任意转换处理
    return data;
  }],
  // `headers` 是即将被发送的⾃定义请求头
  headers: { 'X-Requested-With': 'XMLHttpRequest' },
  // `params` 是即将与请求⼀起发送的 URL 参数
  // 必须是⼀个⽆格式对象(plain object)或 URLSearchParams 对象
  params: {
    ID: 12345
  },
  // `paramsSerializer` 是⼀个负责 `params` 序列化的函数
  // (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param /)
  paramsSerializer: function (params) {
    return Qs.stringify(params, { arrayFormat: 'brackets' })
  },
  // `data` 是作为请求主体被发送的数据
  // 只适⽤于这些请求⽅法 'PUT', 'POST', 和 'PATCH'
  // 在没有设置 `transformRequest` 时,必须是以下类型之⼀:
  // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
  // - 浏览器专属:FormData, File, Blob
  // - Node 专属: Stream
  data: {
    firstName: 'Fred'
  },
  // `timeout` 指定请求超时的毫秒数(0 表示⽆超时时间)
  // 如果请求花费了超过 `timeout` 的时间,请求将被中断
  timeout: 1000,
  // `withCredentials` 表示跨域请求时是否需要使⽤凭证
  withCredentials: false, // default
  // `adapter` 允许⾃定义处理请求,以使测试更轻松
  // 返回⼀个 promise 并应⽤⼀个有效的响应 (查阅 [response docs](#response-api)).
  adapter: function (config) {
    /* ... */
  },
  // `auth` 表示应该使⽤ HTTP 基础验证,并提供凭据
  // 这将设置⼀个 `Authorization` 头,覆写掉现有的任意使⽤ `headers` 设置的⾃定义 `Authorization`头
  auth: {
    username: 'janedoe',
    password: 's00pers3cret'
  },
  // `responseType` 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob','document', 'json', 'text', 'stream'
  responseType: 'json', // default
  // `responseEncoding` indicates encoding to use for decoding responses
  // Note: Ignored for `responseType` of 'stream' or client-side requests
  responseEncoding: 'utf8', // default
  // `xsrfCookieName` 是⽤作 xsrf token 的值的cookie的名称
  xsrfCookieName: 'XSRF-TOKEN', // default
  // `xsrfHeaderName` is the name of the http header that carries the xsrf token value
  xsrfHeaderName: 'X-XSRF-TOKEN', // default
  // `onUploadProgress` 允许为上传处理进度事件
  onUploadProgress: function (progressEvent) {
    // Do whatever you want with the native progress event
  },
  // `onDownloadProgress` 允许为下载处理进度事件
  onDownloadProgress: function (progressEvent) {
    // 对原⽣进度事件的处理
  },
  // `maxContentLength` 定义允许的响应内容的最⼤尺⼨
  maxContentLength: 2000,
  // `validateStatus` 定义对于给定的HTTP 响应状态码是 resolve 或 reject promise 。
  // 如果 `validateStatus` 返回`true`(或者设置为`null` 或`undefined`),promise 将被 resolve; 否则,promise 将被 rejecte
  validateStatus: function (status) {
    return status >= 200 && status < 300; // default
  },
  // `maxRedirects` 定义在 node.js 中 follow 的最⼤重定向数⽬
  // 如果设置为0,将不会 follow 任何重定向
  maxRedirects: 5, // default
  // `socketPath` defines a UNIX Socket to be used in node.js.
  // e.g. '/var/run/docker.sock' to send requests to the docker daemon.
  // Only either `socketPath` or `proxy` can be specified.
  // If both are specified, `socketPath` is used.
  socketPath: null, // default
  // `httpAgent` 和 `httpsAgent` 分别在 node.js 中⽤于定义在执⾏ http 和 https 时使⽤的⾃定义代理。允许像这样配置选项:
  // `keepAlive` 默认没有启⽤
  httpAgent: new http.Agent({ keepAlive: true }),
  httpsAgent: new https.Agent({ keepAlive: true }),
  // 'proxy' 定义代理服务器的主机名称和端⼝
  // `auth` 表示 HTTP 基础验证应当⽤于连接代理,并提供凭据
  // 这将会设置⼀个 `Proxy-Authorization` 头,覆写掉已有的通过使⽤ `header` 设置的⾃定义 `Proxy-Authorization` 头。
  proxy: {
    host: '127.0.0.1',
    port: 9000,
    auth: {
      username: 'mikeymike',
      password: 'rapunz3l'
    }
  },
  // `cancelToken` 指定⽤于取消请求的 cancel token
  // (查看后⾯的 Cancellation 这节了解更多)
  cancelToken: new CancelToken(function (cancel) {
  })
}

1.3.6 测试

请求使用 json-server 启动的服务的数据,只需要执⾏:

const axios = require('axios');

axios
  .get('http://localhost:3000/posts')
  .then(function (response) {
    // handle success
    console.log(response);
  })
  .catch(function (error) {
    // handle error
    console.log(error);
  })
  .then(function () {
    // always executed
  });

1.4 总结

  1. Ajax 是 Async Javascript And Xml 的简称,它是原⽣JavaScript的⼀种请求⽅案,利⽤ XMLHttpRequest 进⾏异步请求数据,实现⽆感刷新数据;

  2. Fetch 是 ES6 新推出的⼀套异步请求⽅案,它天⽣⾃带 Promise,同时也是原⽣的,如果在较⼩项⽬中为了项⽬⼤⼩着想和兼容性不是那么⾼的前提下不妨可以⽤它来进⾏异步请求也是不错的;

  3. Axios 是基于 Ajax 和 Promise 封装的⼀个库,可以利⽤Promise来更好的管控请求回调嵌套造成的回调地狱;