内容简介:使用 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
浏览器支持
桌面浏览器
手机/平板电脑
所有主要的浏览器(除了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返回的响应能告知请求的状态。从上面例子看出请求是成功的( ok
是 true
, status
是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);
第一个参数是设置请求方法(如 post
、 put
或 del
),Fetch会自动设置方法为 get
。
第二个参数是设置头部。因为一般使用JSON数据格式,所以设置 ContentType
为 application/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响应成功,但是仍会有问题出现:
- 可能尝试获取不存在的资源
- 没有权限获取资源
- 输入参数有误
- 服务器抛出异常
- 服务器超时
- 服务器崩溃
- API更改
- ...
假设我们试图获取不存在错误,并了解如何处理错误。下面的例子我将 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));
然而却得到下面这样结果:
获取失败,但是第二个.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" }
大部分是一样的,只有 ok
、 status
和 statusText
是不同的,正如所料,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));这样错误就会进入
catch
语句中。
但是 reject
Promise时,只输出字符串不太好。这样不清楚哪里出错了,你肯定也不会想在异常时,输出下面这样:
让我们在看看响应:
{ 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
中获取 status
或 statusText
,我们可以 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.
光在
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格式的响应,也许会收到下面异常:
这是因为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库,就不需要担心错误处理了。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- RecyclerView使用指南(一)—— 基本使用
- 如何使用Meteorjs使用URL参数
- 使用 defer 还是不使用 defer?
- 使用 Typescript 加强 Vuex 使用体验
- [译] 何时使用 Rust?何时使用 Go?
- UDP协议的正确使用场合(谨慎使用)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。