使用 Fetch

栏目: jQuery · 发布时间: 6年前

内容简介:使用 Fetch

原文链接: https://css-tricks.com/using-fetch/。 本文介绍了Fetch基本使用方法及zlFetch库的使用

无论用JavaScript发送或获取信息,我们都会用到Ajax。Ajax不需要刷新页面就能发送和获取信息,能使网页实现异步更新。

几年前,初始化Ajax一般使用jQuery的 ajax 方法:

$.ajax('some-url', {
  success: (data) => { /* do something with the data */ },
  error: (err) => { /* do something when an error happens */}
});

也可以不用jQuery,但不得不使用 XMLHttpRequest ,然而这是相当复杂

幸亏,浏览器现在支持Fetch API,可以无须其他库就能实现Ajax

浏览器支持

桌面浏览器

使用 Fetch

手机/平板电脑

使用 Fetch

所有主要的浏览器(除了Opera Mini和老的IE)都支持Fetch。针对不支持的,可以使用 Fetch polyfill

Fetch获取数据

使用Fetch获取数据很容易。只需要Fetch你想获取资源。

假设我们想通过GitHub获取一个仓库,我们可以像下面这样使用:

fetch('https://api.github.com/users/chriscoyier/repos');

Fetch会返回Promise,所以在获取资源后,可以使用 .then 方法做你想做的。

fetch('https://api.github.com/users/chriscoyier/repos')
  .then(response => {/* do something */})

如果这是你第一次遇见Fetch,也许惊讶于Fetch返回的 response 。如果 console.log 返回的 response ,会得到下列信息:

{
  body: ReadableStream
  bodyUsed: false
  headers: Headers
  ok : true
  redirected : false
  status : 200
  statusText : "OK"
  type : "cors"
  url : "http://some-website.com/some-url"
  __proto__ : Response
}

可以看出Fetch返回的响应能告知请求的状态。从上面例子看出请求是成功的( oktruestatus 是200),但是我们想获取的仓库名却不在这里。

显然,我们从GitHub请求的资源都存储在 body 中,作为一种可读的流。所以需要调用一个恰当方法将可读流转换为我们可以使用的数据。

Github返回的响应是JSON格式的,所以调用 response.json 方法来转换数据。

还有其他方法来处理不同类型的响应。如果请求一个XML格式文件,则调用 response.text 。如果请求图片,使用 response.blob 方法。

所有这些方法( response.json 等等)返回另一个Promise,所以可以调用 .then 方法处理我们转换后的数据。

fetch('https://api.github.com/users/chriscoyier/repos')
  .then(response => response.json())
  .then(data => {
    // data就是我们请求的repos
    console.log(data)
  });

可以看出Fetch获取数据方法简短并且简单。

接下来,让我们看看如何使用Fetch发送数据。

Fetch发送数据

使用Fetch发送也很简单,只需要配置三个参数。

fetch('some-url', options);

第一个参数是设置请求方法(如 postputdel ),Fetch会自动设置方法为 get

第二个参数是设置头部。因为一般使用JSON数据格式,所以设置 ContentTypeapplication/json

第三个参数是设置包含JSON内容的主体。因为JSON内容是必须的,所以当设置主体时会调用 JSON.stringify

实践中, post 请求会像下面这样:

let content = {some: 'content'};

// The actual fetch request
fetch('some-url', {
  method: 'post',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(content)
})
// .then()...

Fetch处理异常

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

  1. 可能尝试获取不存在的资源
  2. 没有权限获取资源
  3. 输入参数有误
  4. 服务器抛出异常
  5. 服务器超时
  6. 服务器崩溃
  7. API更改
  8. ...

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

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

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

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

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

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

使用 Fetch
获取失败,但是第二个 .then

方法会执行。

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

{
  body: ReadableStream
  bodyUsed: true
  headers: Headers
  ok: false // Response is not ok
  redirected: false
  status: 404 // HTTP status is 404.
  statusText: "Not Found" // Request not found
  type: "cors"
  url: "https://api.github.com/users/chrissycoyier/repos"
}

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

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

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

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

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

// throwing an Error
else {
  throw new Error('something went wrong!')
}

// rejecting a Promise
else {
  return Promise.reject('something went wrong!')
}

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

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

fetch('https://api.github.com/users/chrissycoyier/repos')
  .then(response => {
    if (response.ok) {
      return response.json()
    } else {
      return Promise.reject('something went wrong!')
    }
  })
  .then(data => console.log('data is', data))
  .catch(error => console.log('error is', error));
使用 Fetch
这样错误就会进入 catch

语句中。

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

使用 Fetch

让我们在看看响应:

{
  body: ReadableStream
  bodyUsed: true
  headers: Headers
  ok: false // Response is not ok
  redirected: false
  status: 404 // HTTP status is 404.
  statusText: "Not Found" // Request not found
  type: "cors"
  url: "https://api.github.com/users/chrissycoyier/repos"
}

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

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

fetch('some-url')
  .then(response => {
    if (response.ok) {
      return response.json()
    } else {
      return Promise.reject({
        status: response.status,
        statusText: response.statusText
      })
    }
  })
  .catch(error => {
    if (error.status === 404) {
      // do something about 404
    }
  })

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

  • 401: Unauthorized
  • 404: Not found
  • 408: Connection timeout
  • ...

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

  • 400:Bad request

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

    使用 Fetch

    光在 catch 中告诉状态及原因短语并不足够。我们需要知道缺少什么参数。

    所以服务器需要返回一个对象,告诉造成错误请求原因。如果使用Node和Express,会返回像下面这样的响应:

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

无法在最初的 .then 方法中 reject ,因为错误对象需要 response.json 来解析。

解决的方法是需要两个 then 方法。这样可以首先通过 response.json 读取,然后决定怎么处理。

fetch('some-error')
  .then(handleResponse)

function handleResponse(response) {
  return response.json()
    .then(json => {
      if (response.ok) {
        return json
      } else {
        return Promise.reject(json)
      }
    })
}

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

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

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

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

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

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

处理其他响应类型

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

至于其他的10%呢?

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

使用 Fetch

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

.then(response => {
  let contentType = response.headers.get('content-type')

  if (contentType.includes('application/json')) {
    return response.json()
    // ...
  }

  else if (contentType.includes('text/html')) {
    return response.text()
    // ...
  }

  else {
    // Handle other responses accordingly...
  }
});

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

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

fetch('some-url')
  .then(handleResponse)
  .then(data => console.log(data))
  .then(error => console.log(error))

function handleResponse (response) {
  let contentType = response.headers.get('content-type')
  if (contentType.includes('application/json')) {
    return handleJSONResponse(response)
  } else if (contentType.includes('text/html')) {
    return handleTextResponse(response)
  } 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`)
  }
}

function handleJSONResponse (response) {
  return response.json()
    .then(json => {
      if (response.ok) {
        return json
      } else {
        return Promise.reject(Object.assign({}, json, {
          status: response.status,
          statusText: response.statusText
        }))
      }
    })
}
function handleTextResponse (response) {
  return response.text()
    .then(text => {
      if (response.ok) {
        return json
      } else {
        return Promise.reject({
          status: response.status,
          statusText: response.statusText,
          err: text
        })
      }
    })
}

介绍zlFetch

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

典型的zlfetch像下面这样:

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

使用之前,需要安装zlFetch

npm install zl-fetch --save

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

// Polyfills (if needed)
require('isomorphic-fetch') // or whatwg-fetch or node-fetch if you prefer

// ES6 Imports
import zlFetch from 'zl-fetch';

// CommonJS Imports
const zlFetch = require('zl-fetch');

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

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

let content = {some: 'content'}

// Post request with fetch
fetch('some-url', {
  method: 'post',
  headers: {'Content-Type': 'application/json'}
  body: JSON.stringify(content)
});

// Post request with zlFetch
zlFetch('some-url', {
  method: 'post',
  body: content
});

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

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

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

let token = 'someToken'
zlFetch('some-url', {
  headers: {
    Authorization: `Bearer ${token}`
  }
});

// Authentication with JSON Web Tokens with zlFetch
zlFetch('some-url', {token});

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

总结

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

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

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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

引爆点

引爆点

[美] 马尔科姆·格拉德威尔 / 钱清、覃爱冬 / 中信出版社 / 2006-1 / 29.80元

这本书是《纽约客》杂志专职作家马尔科姆·格拉德威尔的一部才华横溢之作。他以社会上突如其来的流行风潮研究为切入点,从一个全新的角度探索了控制科学和营销模式。他认为,思想、行为、信息以及产品常常会像传染病爆发一样,迅速传播蔓延。正如一个病人就能引起一场全城流感;如果个别工作人员对顾客大打出手,或几位涂鸦爱好者管不住自己,也能在地铁里掀起一场犯罪浪潮;一位满意而归的顾客还能让新开张的餐馆座无虚席。这些现......一起来看看 《引爆点》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

MD5 加密
MD5 加密

MD5 加密工具