您现在的位置: 365建站网 > 365文章 > fetch使用教程整理和XMLHttpRequest的区别

fetch使用教程整理和XMLHttpRequest的区别

文章来源:365jz.com     点击数:747    更新时间:2018-07-07 10:48   参与评论

fetch 使用

1. 浏览器支持情况

fetch是相对较新的技术,当然就会存在浏览器兼容性的问题,当前各个浏览器低版本的情况下都是不被支持的,因此为了在所有主流浏览器中使用fetch 需要考虑 fetch 的 polyfill 了

   require('es6-promise').polyfill();    require('isomorphic-fetch');12

引入这两个文件,就可以支持主流浏览器了


fetch和XMLHttpRequest

如果看网上的fetch教程,会首先对比XMLHttpRequest和fetch的优劣,然后引出一堆看了很快会忘记的内容(本人记性不好)。因此,我写一篇关于fetch的文章,为了自己看着方便,毕竟工作中用到的也就是一些很基础的点而已。

fetch,说白了,就是XMLHttpRequest的一种替代方案。如果有人问你,除了Ajax获取后台数据之外,还有没有其他的替代方案?

这是你就可以回答,除了XMLHttpRequest对象来获取后台的数据之外,还可以使用一种更优的解决方案fetch。

如何获取fetch

到现在为止,fetch的支持性还不是很好,但是在谷歌浏览器中已经支持了fetch。fetch挂在在BOM中,可以直接在谷歌浏览器中使用。

查看fetch的支持情况:fetch的支持情况

当然,如果不支持fetch也没有问题,可以使用第三方的ployfill来实现只会fetch:whatwg-fetch

fetch的helloworld

下面我们来写第一个fetch获取后端数据的例子:

// 通过fetch获取百度的错误提示页面

</>code

  1. fetch('https://www.baidu.com/search/error.html') // 返回一个Promise对象
  2.   .then((res)=>{    return res.text() // res.text()是一个Promise对象
  3.   })
  4.   .then((res)=>{    console.log(res) // res是最终的结果
  5.   })

是不是很简单?可能难的地方就是Promise的写法,这个可以看阮一峰老师的ES6教程来学习。

说明一点,下面演示的GET请求或POST请求,都是采用百度中查询到的一些接口,可能传递的有些参数这个接口并不会解析,但不会影响这个接口的使用。

GET请求

GET请求初步

完成了helloworld,这个时候就要来认识一下GET请求如何处理了。

上面的helloworld中这是使用了第一个参数,其实fetch还可以提供第二个参数,就是用来传递一些初始化的信息。

这里如果要特别指明是GET请求,就要写成下面的形式:

</>code

  1. // 通过fetch获取百度的错误提示页面
  2. fetch('https://www.baidu.com/search/error.html', { 
  3.    method: 'GET'
  4.  })
  5.  .then((res)=>{    return res.text()
  6.  })
  7.  .then((res)=>{    console.log(res)
  8.  })

GET请求的参数传递

GET请求中如果需要传递参数怎么办?这个时候,只能把参数写在URL上来进行传递了。

</>code

  1. // 通过fetch获取百度的错误提示页面
  2. fetch('https://www.baidu.com/search/error.html?a=1&b=2', { // 在URL中写上传递的参数
  3.    method: 'GET'
  4.  })
  5.  .then((res)=>{    return res.text()
  6.  })
  7.  .then((res)=>{    console.log(res)
  8.  })

POST请求

POST请求初步

与GET请求类似,POST请求的指定也是在fetch的第二个参数中:

</>code

  1. // 通过fetch获取百度的错误提示页面
  2. fetch('https://www.baidu.com/search/error.html', {
  3.     method: 'POST' // 指定是POST请求
  4.  })
  5.  .then((res)=>{    return res.text()
  6.  })
  7.  .then((res)=>{    console.log(res)
  8.  })

POST请求参数的传递

众所周知,POST请求的参数,一定不能放在URL中,这样做的目的是防止信息泄露。

</>code

  1. // 通过fetch获取百度的错误提示页面
  2. fetch('https://www.baidu.com/search/error.html', {
  3.     method: 'POST', 
  4.    body: new URLSearchParams([["foo", 1],["bar", 2]]).toString() // 这里是请求对象
  5.  })
  6.  .then((res)=>{    return res.text()
  7.  })
  8.  .then((res)=>{    console.log(res)
  9.  })

其实除了对象URLSearchParams外,还有几个其他的对象,可以参照:常用的几个对象来学习使用。

设置请求的头信息

在POST提交的过程中,一般是表单提交,可是,经过查询,发现默认的提交方式是:Content-Type:text/plain;charset=UTF-8,这个显然是不合理的。下面咱们学习一下,指定头信息:

</>code

  1. // 通过fetch获取百度的错误提示页面
  2. fetch('https://www.baidu.com/search/error.html', { 
  3.    method: 'POST', 
  4.    headers: new Headers({      'Content-Type': 'application/x-www-form-urlencoded' // 指定提交方式为表单提交
  5.    }),
  6.    body: new URLSearchParams([["foo", 1],["bar", 2]]).toString()
  7.  })
  8.  .then((res)=>{    return res.text()
  9.  })
  10.  .then((res)=>{    console.log(res)
  11.  })

这个时候,在谷歌浏览器的Network中查询,会发现,请求方式已经变成了content-type:application/x-www-form-urlencoded。

通过接口得到JSON数据

上面所有的例子中都是返回一个文本,那么除了文本,有没有其他的数据类型呢?肯定是有的,具体查询地址:Body的类型

由于最常用的是JSON数据,那么下面就简单演示一下获取JSON数据的方式:

</>code

  1. // 通过fetch获取百度的错误提示页面
  2. fetch('https://www.baidu.com/rec?platform=wise&ms=1&rset=rcmd&word=123&qid=11327900426705455986&rq=123&from=844b&baiduid=A1D0B88941B30028C375C79CE5AC2E5E%3AFG%3D1&tn=&clientWidth=375&t=1506826017369&r=8255', { // 在URL中写上传递的参数
  3.    method: 'GET', 
  4.    headers: new Headers({      'Accept': 'application/json' // 通过头指定,获取的数据类型是JSON
  5.    })
  6.  })
  7.  .then((res)=>{    return res.json() // 返回一个Promise,可以解析成JSON
  8.  })
  9.  .then((res)=>{    console.log(res) // 获取JSON数据
  10.  })

强制带Cookie

默认情况下, fetch 不会从服务端发送或接收任何 cookies, 如果站点依赖于维护一个用户会话,则导致未经认证的请求(要发送 cookies,必须发送凭据头).

</>code

  1. // 通过fetch获取百度的错误提示页面
  2. fetch('https://www.baidu.com/search/error.html', { 
  3.    method: 'GET',
  4.    credentials: 'include' // 强制加入凭据头
  5.  })
  6.  .then((res)=>{    return res.text()
  7.  })
  8.  .then((res)=>{    console.log(res)
  9.  })

简单封装一下fetch

最后了,介绍了一大堆内容,有没有发现,在GET和POST传递参数的方式不同呢?下面咱们就来封装一个简单的fetch,来实现GET请求和POST请求参数的统一。

</>code

  1. /**
  2.  * 将对象转成 a=1&b=2的形式
  3.  * @param obj 对象
  4.  */
  5. function obj2String(obj, arr = [], idx = 0) {
  6.   for (let item in obj) {
  7.     arr[idx++] = [item, obj[item]]
  8.   }
  9.   return new URLSearchParams(arr).toString()
  10. }
  11. /**
  12.  * 真正的请求
  13.  * @param url 请求地址
  14.  * @param options 请求参数
  15.  * @param method 请求方式
  16.  */
  17. function commonFetcdh(url, options, method = 'GET') {
  18.   const searchStr = obj2String(options)
  19.   let initObj = {}
  20.   if (method === 'GET') { // 如果是GET请求,拼接url
  21.     url += '?' + searchStr
  22.     initObj = {
  23.       method: method,
  24.       credentials: 'include'
  25.     }
  26.   } else {
  27.     initObj = {
  28.       method: method,
  29.       credentials: 'include',
  30.       headers: new Headers({
  31.         'Accept': 'application/json',
  32.         'Content-Type': 'application/x-www-form-urlencoded'
  33.       }),
  34.       body: searchStr
  35.     }
  36.   }
  37.   fetch(url, initObj).then((res) => {
  38.     return res.json()
  39.   }).then((res) => {
  40.     return res
  41.   })
  42. }
  43. /**
  44.  * GET请求
  45.  * @param url 请求地址
  46.  * @param options 请求参数
  47.  */
  48. function GET(url, options) {
  49.   return commonFetcdh(url, options, 'GET')
  50. }
  51. /**
  52.  * POST请求
  53.  * @param url 请求地址
  54.  * @param options 请求参数
  55.  */
  56. function POST(url, options) {
  57.   return commonFetcdh(url, options, 'POST')
  58. }

</>code

  1. GET('https://www.baidu.com/search/error.html', {a:1,b:2})
  2. POST('https://www.baidu.com/search/error.html', {a:1,b:2})


 API

</>code

  1.     fetch(url,{ // url: 请求地址
  2.         method: "GET", // 请求的方法POST/GET等
  3.         headers : { // 请求头(可以是Headers对象,也可是JSON对象)
  4.             'Content-Type': 'application/json',
  5.             'Accept': 'application/json'
  6.         }, 
  7.         body: , // 请求发送的数据 blob、BufferSource、FormData、URLSearchParams(get或head方法中不能包含body)
  8.         cache : 'default', // 是否缓存这个请求
  9.         credentials : 'same-origin', //要不要携带 cookie 默认不携带 omit、same-origin 或者 include
  10.         mode : "", 
  11.         /*  
  12.             mode,给请求定义一个模式确保请求有效
  13.             same-origin:只在请求同域中资源时成功,其他请求将被拒绝(同源策略)
  14.             cors : 允许请求同域及返回CORS响应头的域中的资源,通常用作跨域请求来从第三方提供的API获取数据
  15.             cors-with-forced-preflight:在发出实际请求前执行preflight检查
  16.             no-cors : 目前不起作用(默认)
  17.         */
  18.     }).then(resp => {
  19.         /*
  20.             Response 实现了 Body, 可以使用 Body 的 属性和方法:
  21.             resp.type // 包含Response的类型 (例如, basic, cors).
  22.             resp.url // 包含Response的URL.
  23.             resp.status // 状态码
  24.             resp.ok // 表示 Response 的成功还是失败
  25.             resp.headers // 包含此Response所关联的 Headers 对象 可以使用
  26.             resp.clone() // 创建一个Response对象的克隆
  27.             resp.arrayBuffer() // 返回一个被解析为 ArrayBuffer 格式的promise对象
  28.             resp.blob() // 返回一个被解析为 Blob 格式的promise对象
  29.             resp.formData() // 返回一个被解析为 FormData 格式的promise对象
  30.             resp.json() // 返回一个被解析为 Json 格式的promise对象
  31.             resp.text() // 返回一个被解析为 Text 格式的promise对象
  32.         */ 
  33.         if(resp.status === 200) return resp.json(); 
  34.         // 注: 这里的 resp.json() 返回值不是 js对象,通过 then 后才会得到 js 对象
  35.         throw New Error ('false of json');
  36.     }).then(json => {
  37.         console.log(json);
  38.     }).catch(error => {
  39.         consolr.log(error);
  40.     })

常用情况

1. 请求 json

</>code

  1.    fetch('http://xxx/xxx.json').then(res => {        return res.json();
  2.     }).then(res => {
  3.         console.log(res);
  4.     })

2. 请求文本

</>code

  1.    fetch('/xxx/page').then(res => {        return res.text();
  2.     }).then(res => {
  3.         console.log(res);
  4.     })

3. 发送普通 json 数据

</>code

  1.    fetch('/xxx', {
  2.         method: 'post',
  3.         body: JSON.stringify({
  4.             username: '',
  5.             password: ''
  6.         })
  7.     });

4. 发送form 表单数据

</>code

  1.    var form = document.querySelector('form');
  2.     fetch('/xxx', {
  3.         method: 'post',
  4.         body: new FormData(form)
  5.     });

5. 获取图片

</>code

  1. URL.createObjectURL()
  2.    fetch('/xxx').then(res => {        return res.blob();
  3.    }).then(res => {
  4.        document.querySelector('img').src = URL.createObjectURL(imageBlob);
  5.    })

6. 上传

</>code

  1.    var file = document.querySelector('.file')    var data = new FormData()
  2.     data.append('file', file.files[0])
  3.     fetch('/xxx', {
  4.       method: 'POST',
  5.       body: data
  6.     })

4. 封装

</>code

  1.    require('es6-promise').polyfill();    require('isomorphic-fetch');
  2.     export default function request(method, url, body) {
  3.         method = method.toUpperCase();        if (method === 'GET') {
  4.             body = undefined;
  5.         } else {
  6.             body = body && JSON.stringify(body);
  7.         }        return fetch(url, {
  8.             method,
  9.             headers: {                'Content-Type': 'application/json',                'Accept': 'application/json'
  10.             },
  11.             body
  12.         }).then((res) => {            if (res.status >= 200 && res.status < 300) {                return res;
  13.             } else {                return Promise.reject('请求失败!');
  14.             }
  15.         })
  16.     }
  17.     export const get = path => request('GET', path);
  18.     export const post = (path, body) => request('POST', path, body);
  19.     export const put = (path, body) => request('PUT', path, body);
  20.     export const del = (path, body) => request('DELETE', path, body);



Fetch处理异常

虽然希望Ajax响应成功,但是仍会有问题出现:

  1. 可能尝试获取不存在的资源

  2. 没有权限获取资源

  3. 输入参数有误

  4. 服务器抛出异常

  5. 服务器超时

  6. 服务器崩溃

  7. API更改

  8. ...

假设我们试图获取不存在错误,并了解如何处理错误。下面的例子我将chriscoyier拼错为chrissycoyier

</>code

  1. // 获取chrissycoyier's repos 而不是 chriscoyier's repos
  2. fetch('https://api.github.com/users/chrissycoyier/repos')

为了处理此错误,我们需要使用catch方法。

也许我们会用下面这种方法:

</>code

  1. fetch('https://api.github.com/users/chrissycoyier/repos')
  2.   .then(response => response.json())
  3.   .then(data => console.log('data is', data))
  4.   .catch(error => console.log('error is', error));

然而却得到下面这样结果:


获取失败,但是第二个.then方法会执行。

 

如果console.log此次响应,会看出不同:

</>code

  1.   {  body: ReadableStream
  2.   bodyUsed: true
  3.   headers: Headers
  4.   ok: false // Response is not ok
  5.   redirected: false
  6.   status: 404 // HTTP status is 404.
  7.   statusText: "Not Found" // Request not found
  8.   type: "cors"
  9.   url: "https://api.github.com/users/chrissycoyier/repos"
  10. }

大部分是一样的,只有okstatusstatusText是不同的,正如所料,GitHub上没有发现chrissycoyier

上面响应告诉我们Fetch不会关心AJAX是否成功,他只关心从服务器发送请求和接收响应,如果响应失败我们需要抛出异常。

因此,初始的then方法需要被重写,以至于如果响应成功会调用response.json。最简单方法是检查response是否为ok

</>code

  1. fetch('some-url')
  2.   .then(response => {    if (response.ok) {      return response.json()
  3.     } else {      // Find some way to get to execute .catch()
  4.     }
  5.   });

一旦我们知道请求是不成功的,我可以throw异常或rejectPromise来调用catch

</>code

  1. // throwing an Errorelse {  throw new Error('something went wrong!')
  2. }// rejecting a Promiseelse {  return Promise.reject('something went wrong!')
  3. }

这里选择Promise.reject,是因为容易扩展。抛出异常方法也不错,但是无法扩展,唯一益处在于便于栈跟踪。

所以,到现在代码应该是这样的:

</>code

  1. fetch('https://api.github.com/users/chrissycoyier/repos')
  2.   .then(response => {    if (response.ok) {      return response.json()
  3.     } else {      return Promise.reject('something went wrong!')
  4.     }
  5.   })
  6.   .then(data => console.log('data is', data))
  7.   .catch(error => console.log('error is', error));

 


这样错误就会进入catch语句中。

 

但是rejectPromise时,只输出字符串不太好。这样不清楚哪里出错了,你肯定也不会想在异常时,输出下面这样:

 

让我们在看看响应:

</>code

  1.   {  body: ReadableStream
  2.   bodyUsed: true
  3.   headers: Headers
  4.   ok: false // Response is not ok
  5.   redirected: false
  6.   status: 404 // HTTP status is 404.
  7.   statusText: "Not Found" // Request not found
  8.   type: "cors"
  9.   url: "https://api.github.com/users/chrissycoyier/repos"
  10. }

在这个例子中,我们知道资源是不存在。所以我们可以返回404状态或Not Found原因短语,然而我们就知道如何处理。

为了在.catch中获取statusstatusText,我们可以reject一个JavaScript对象:

</>code

  1. fetch('some-url')
  2.   .then(response => {    if (response.ok) {      return response.json()
  3.     } else {      return Promise.reject({        status: response.status,        statusText: response.statusText
  4.       })
  5.     }
  6.   })
  7.   .catch(error => {    if (error.status === 404) {      // do something about 404
  8.     }
  9.   })

上面的错误处理方法对于下面这些不需要解释的HTTP状态很适用。

  • 401: Unauthorized

  • 404: Not found

  • 408: Connection timeout

  • ...

但对于下面这些特定的错误不适用:

  • 400:Bad request
    例如,如果请求错误缺少必要的参数,就会返回400.

    光在catch中告诉状态及原因短语并不足够。我们需要知道缺少什么参数。
    所以服务器需要返回一个对象,告诉造成错误请求原因。如果使用Node和Express,会返回像下面这样的响应:

</>code

  1. res.status(400).send({  err: 'no first name'
  2. })

无法在最初的.then方法中reject,因为错误对象需要response.json来解析。
解决的方法是需要两个then方法。这样可以首先通过response.json读取,然后决定怎么处理。

</>code

  1. fetch('some-error')
  2.   .then(handleResponse)function handleResponse(response) {  return response.json()
  3.     .then(json => {      if (response.ok) {        return json
  4.       } else {        return Promise.reject(json)
  5.       }
  6.     })
  7. }

首先我们调用response.json读取服务器发来的JSON数据,response.json返回Promise,所以可以链式调用.then方法。

在第一个.then中调用第二个.then,因为我们仍希望通过repsonse.ok判断响应是否成功。

如果想发送状态和原因短语,可以使用Object.assign()将二者结合为一个对象。

</>code

  1. let error = Object.assign({}, json, {  status: response.status,  statusText: response.statusText
  2. })return Promise.reject(error)

可以使用这样新的handleResponse函数,让数据能自动的进入.then.catch中。

</>code

  1. fetch('some-url')
  2.   .then(handleResponse)
  3.   .then(data => console.log(data))
  4.   .catch(error => console.log(error))

处理其他响应类型

到现在,我们只处理JSON格式的响应,而返回JSON格式数据大约占90%。

至于其他的10%呢?

假设上面的例子返回的是XML格式的响应,也许会收到下面异常:

 

这是因为XML格式不是JSON格式,我们无法使用response.json,事实上,我们需要response.text,所以我们需要通过判断响应的头部来决定内容格式:

</>code

  1. .then(response => {  let contentType = response.headers.get('content-type')  if (contentType.includes('application/json')) {    return response.json()    // ...
  2.   }  else if (contentType.includes('text/html')) {    return response.text()    // ...
  3.   }  else {    // Handle other responses accordingly...
  4.   }
  5. });

当我遇见这种问题时,我尝试使用ExpressJWT处理身份验证,我不知道可以发生JSON响应数据,所以我将XML格式设为默认。

这是我们到现在完整代码:

</>code

  1. fetch('some-url')
  2.   .then(handleResponse)
  3.   .then(data => console.log(data))
  4.   .then(error => console.log(error))function handleResponse (response) {  let contentType = response.headers.get('content-type')  if (contentType.includes('application/json')) {    return handleJSONResponse(response)
  5.   } else if (contentType.includes('text/html')) {    return handleTextResponse(response)
  6.   } else {    // Other response types as necessary. I haven't found a need for them yet though.    throw new Error(`Sorry, content-type ${contentType} not supported`)
  7.   }
  8. }function handleJSONResponse (response) {  return response.json()
  9.     .then(json => {      if (response.ok) {        return json
  10.       } else {        return Promise.reject(Object.assign({}, json, {          status: response.status,          statusText: response.statusText
  11.         }))
  12.       }
  13.     })
  14. }function handleTextResponse (response) {  return response.text()
  15.     .then(text => {      if (response.ok) {        return json
  16.       } else {        return Promise.reject({          status: response.status,          statusText: response.statusText,          err: text
  17.         })
  18.       }
  19.     })
  20. }

介绍zlFetch

zlFetch库就是上例中handleResponse函数,所以可以不用生成此函数,不需要担心响应来处理数据和错误。

典型的zlfetch像下面这样:

</>code

  1. zlFetch('some-url', options)
  2.   .then(data => console.log(data))
  3.   .catch(error => console.log(error));

使用之前,需要安装zlFetch

</>code

  1.  npm install zl-fetch --save

接着,需要引入到你的代码中,如果你需要polyfill,确保加入zlFetch之前引入它。

</>code

  1.  // Polyfills (if needed)require('isomorphic-fetch') // or whatwg-fetch or node-fetch if you prefer// ES6 Importsimport zlFetch from 'zl-fetch';// CommonJS Importsconst zlFetch = require('zl-fetch');

zlFetch还能无须转换成JSON格式就能发送JSON数据。

下面两个函数做了同样事情,zlFetch加入Content-type然后将内容转换为JSON格式。

</>code

  1. let content = {some: 'content'}// Post request with fetch
  2. fetch('some-url', {  method: 'post',  headers: {'Content-Type': 'application/json'}
  3.   body: JSON.stringify(content)
  4. });// Post request with zlFetch
  5. zlFetch('some-url', {  method: 'post',  body: content
  6. });

zlFetch处理身份认证也很容易。

常用方法是在头部加入Authorization,其值设为Bearer your-token-here。如果你需要增加token选项,zlFetch会帮你创建此域。

所以,下面两种代码是一样的:

</>code

  1. let token = 'someToken'
  2. zlFetch('some-url', {  headers: {    Authorization: `Bearer ${token}`
  3.   }
  4. });// Authentication with JSON Web Tokens with zlFetch
  5. zlFetch('some-url', {token});

下面就是使用zlFetch来从GitHub上获取repos:


总结

Fetch是很好的方法,能发送和接收数据。不需要在编写XHR请求或依赖于jQuery。

尽管Fetch很好,但是其错误处理不是很直接。在处理之前,需要让错误信息进入到catch方法中。

使用zlFetch库,就不需要担心错误处理了。

如对本文有疑问,请提交到交流论坛,广大热心网友会为你解答!! 点击进入论坛

发表评论 (747人查看0条评论)
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
昵称:
最新评论
------分隔线----------------------------

快速入口

· 365软件
· 杰创官网
· 建站工具
· 网站大全

其它栏目

· 建站教程
· 365学习

业务咨询

· 技术支持
· 服务时间:9:00-18:00
365建站网二维码

Powered by 365建站网 RSS地图 HTML地图

copyright © 2013-2024 版权所有 鄂ICP备17013400号