fetch() API 正在进入 window 对象,并正在查找替换 XHR
XMLHttpRequest 很长
fetch()
允许您发出类似于 XMLHttpRequest (XHR) 的网络请求。主要区别在于,Fetch API 使用 promise,它可以实现更简洁、更简洁的 API,避免回调异常,并且必须记住 XMLHttpRequest 的复杂 API。
从 Chrome 40 开始,Fetch API 便已在 Service Worker 全局范围内使用,但在 Chrome 42 中,它将在窗口作用域内启用。您还可以立即使用一个非常实用的 GitHub 提供的 polyfill。
如果您之前从未使用过 promise,请参阅 JavaScript promise 简介。
基本提取请求
首先,我们来比较一个使用 XMLHttpRequest
和 fetch
实现的简单示例。我们只想请求一个网址,获得一个响应,然后将其解析为 JSON。
XMLHttpRequest
XMLHttpRequest
需要设置两个监听器来处理成功和错误情况,并调用 open()
和 send()
。MDN 文档中的示例。
function reqListener() {
var data = JSON.parse(this.responseText);
console.log(data);
}
function reqError(err) {
console.log('Fetch Error :-S', err);
}
var oReq = new XMLHttpRequest();
oReq.onload = reqListener;
oReq.onerror = reqError;
oReq.open('get', './api/some.json', true);
oReq.send();
提取
我们的提取请求如下所示:
fetch('./api/some.json')
.then(
function(response) {
if (response.status !== 200) {
console.log('Looks like there was a problem. Status Code: ' +
response.status);
return;
}
// Examine the text in the response
response.json().then(function(data) {
console.log(data);
});
}
)
.catch(function(err) {
console.log('Fetch Error :-S', err);
});
我们首先检查响应状态是否为 200,然后再将响应解析为 JSON。
fetch()
请求的响应是一个 Stream 对象,这意味着当我们调用 json()
方法时,系统会返回 Promise,因为系统将异步读取数据流。
响应元数据
在前面的示例中,我们了解了 Response 对象的状态以及如何将响应解析为 JSON。下图说明了我们可能想要访问的其他元数据(例如标头)。
fetch('users.json').then(function(response) {
console.log(response.headers.get('Content-Type'));
console.log(response.headers.get('Date'));
console.log(response.status);
console.log(response.statusText);
console.log(response.type);
console.log(response.url);
});
响应类型
当我们发出提取请求时,响应的 response.type
将为“basic
”“cors
”或“opaque
”。这些 types
指示资源的来源,并可用于告知应如何处理响应对象。
请求同一来源的资源时,响应将具有 basic
类型,并且您可以从响应中查看内容,没有任何限制。
如果请求获取的另一个源上的资源会返回 COR 标头,则类型为 cors
。cors
和 basic
响应几乎完全相同,只不过 cors
响应将您可以查看的标头限制为 Cache-Control
、Content-Language
、Content-Type
、Expires
、Last-Modified
和 Pragma
。
opaque
响应针对的是针对其他来源上的未返回 CORS 标头的资源发出的请求。对于不透明响应,我们无法读取返回的数据或查看请求的状态,这意味着我们无法检查请求是否成功。
您可以为提取请求定义一种模式,以便仅解析特定请求。您可以设置的模式如下所示:
- 只有针对同一来源的资产的请求,
same-origin
才会成功,所有其他请求都将被拒绝。 cors
将允许针对同源资源和其他源资源(会返回相应 COR 标头)的请求。cors-with-forced-preflight
始终会在发出实际请求之前执行预检检查。no-cors
旨在向没有 CORS 标头的其他来源发出请求并产生opaque
响应,但如前所述,这在窗口全局范围内是不可行的。
如需定义模式,请添加一个选项对象作为 fetch
请求的第二个参数,并在该对象中定义模式:
fetch('http://some-site.com/cors-enabled/some.json', {mode: 'cors'})
.then(function(response) {
return response.text();
})
.then(function(text) {
console.log('Request successful', text);
})
.catch(function(error) {
log('Request failed', error)
});
链接 Promise
promise 的一项出色功能是能够将它们链接在一起。对于提取,这允许您在提取请求之间共享逻辑。
如果您使用的是 JSON API,则需要检查状态并解析每个响应的 JSON。您可以通过在返回 promise 的不同函数中定义状态和 JSON 解析来简化代码,而不必费心处理最终数据和错误情况。
function status(response) {
if (response.status >= 200 && response.status < 300) {
return Promise.resolve(response)
} else {
return Promise.reject(new Error(response.statusText))
}
}
function json(response) {
return response.json()
}
fetch('users.json')
.then(status)
.then(json)
.then(function(data) {
console.log('Request succeeded with JSON response', data);
}).catch(function(error) {
console.log('Request failed', error);
});
我们定义了 status
函数,该函数会检查 response.status 并返回 Promise.resolve()
或 Promise.reject()
的结果,后者会返回已解析或遭到拒绝的 Promise。这是 fetch()
链中调用的第一个方法;如果解析成功,则调用 json()
方法,该方法将再次从 response.json()
调用返回 Promise。然后,我们得到一个包含已解析的 JSON 的对象。如果解析失败,promise 将遭拒,并执行 catch 语句。
这样做的好处在于,您可以在所有提取请求之间共享逻辑,使代码更易于维护、阅读和测试。
POST 请求
Web 应用需要使用 POST 方法调用 API 并在请求正文中提供一些参数,这种情况并不少见。
为此,我们可以在 fetch()
选项中设置 method
和 body
参数。
fetch(url, {
method: 'post',
headers: {
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
},
body: 'foo=bar&lorem=ipsum'
})
.then(json)
.then(function (data) {
console.log('Request succeeded with JSON response', data);
})
.catch(function (error) {
console.log('Request failed', error);
});
使用提取请求发送凭据
如果您想使用 Cookie 等凭据发出提取请求,则应将请求的 credentials
设置为 "include"
。
fetch(url, {
credentials: 'include'
})
常见问题解答
如何取消 fetch() 请求?
目前无法取消提取,但 GitHub 上正在讨论这一点。H/T @jaffathecake。
有 polyfill?
GitHub 提供了一个用于提取的 polyfill。H/T @Nexii 指出这一点。
为什么 Service Worker 支持“no-cors”,而窗口不支持?
这是出于安全方面的考虑,您可以点击此处了解详情。