目录
(1)、通过手动控制 promise 状态的实例来实现 fetch 的 timeout 功能
(2)、利用 Promise.race 方法代替实现 fetch 的 timeout 的功能
3、解决 fetch 不支持进度事件(Progress Event)
(1)、利用 response.body 模拟实现 fetch 的 progress 事件
(2)、使用 Promise+XHR 结合的方式实现类 fetch 的 progress 效果
一、fetch 概述
fetch 是一种 HTTP 数据请求的方式,它不是 ajax 。 fetch 是 XMLHttpRequest(以下简称 XHR)的一种替代方案。
fetch 与 ajax 的区别:
- fetch() 方法是原生的 JavaScript 方法,可以直接使用,而 ajax 需要二次封装成一个方法才更便于使用。
- fetch() 方法会返回的一个 promise 对象,它不会拒绝 http 的错误状态,即使响应是一个HTTP 404 或 500,Promise 状态也会被标记为 resolve,但是会将 resolve 的返回值的 ok 属性设置为 false。当仅当网络故障时或请求被阻止时,才会标记为 reject。而 ajax,默认返回的是一个普通对象。
- 在默认情况下,fetch() 方法不会接受或者发送 cookies。只有手动设置 credentials 为 include 时,才可以发送 cookies。而 ajax,允许接收和发送 cookies。
- fetch() 方法不支持超时(timeout)处理。而 ajax,支持超时处理。
二、fetch 的语法
1、实现一个简单的 fetch 请求
fetch('http://example.com/movies.json')
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(myJson);
});
2、fetch 方法介绍
fetch 方法接受两个参数:一个 URL 地址或一个 request 对象 和 (可选的)一个配置项对象。
(1)、fetch 方法的第一个参数
除了传给 fetch() 一个 URL 地址,还可以通过使用 Request() 构造函数来创建一个 request 对象,然后再作为参数传给 fetch() 方法。
var myHeaders = new Headers();
var myInit = { method: 'GET',
headers: myHeaders,
mode: 'cors',
cache: 'default' };
var myRequest = new Request('flowers.jpg', myInit);
fetch(myRequest).then(function(response) {
return response.blob();
}).then(function(myBlob) {
var objectURL = URL.createObjectURL(myBlob);
myImage.src = objectURL;
});
(2)、fetch 方法的第二个参数
(可选的)一个配置项对象。该配置项包括所有对请求的设置。
配置项可选的参数有:
- method:请求使用的方法,可选的值有 GET、POST、 PUT、 DELETE、OPTION、HEAD等。
- headers:请求的头信息,形式为 Headers 的对象或包含 ByteString 值的对象字面量。
- body:请求的 body 信息——可能是一个 Blob、BufferSource、FormData、URLSearchParams 或者 USVString 对象。必须与'Content-Type'标头匹配。注意 GET 或 HEAD 方法的请求不能包含 body 信息。
- mode:请求的模式,可选的值有 cors、no-cors 或者 same-origin。
- credentials:请求的资格证书,可选的值有 omit、same-origin 或者 include。
- omit:默认值,忽略 cookie 的发送;
- same-origin:表示 cookie 只能同域发送,不能跨域发送;
- include:cookie 既可以同域发送,也可以跨域发送。
- cache: 请求的 cache 模式 default、no-store、reload、no-cache、force-cache 或者 only-if-cached 。
- redirect:可用的 redirect 模式: follow (自动重定向),error (如果产生重定向将自动终止并且抛出一个错误),或者 manual (手动处理重定向). 在Chrome中,Chrome 47之前的默认值是 follow,从 Chrome 47开始是 manual。
- referrer:一个 USVString 可以是 no-referrer、client或一个 URL。默认是 client。
- referrerPolicy:指定了HTTP头部referer字段的值。可能为以下值之一: no-referrer、no-referrer-when-downgrade、origin、origin-when-cross-origin、unsafe-url 。
- integrity:包括请求的 subresource integrity 值( 例如: sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=)。
举个栗子:
// POST方法实现示例
postData('http://example.com/answer', {answer: 42})
.then(data => console.log(data))
.catch(error => console.error(error))
function postData(url, data) {
// 默认值标记为 *
return fetch(url, {
body: JSON.stringify(data), // 必须与'Content-Type'标头匹配
cache: 'no-cache', // 可选的值有*default, no-cache, reload, force-cache, only-if-cached。
credentials: 'same-origin', // 可选的值有include, same-origin, *omit
headers: {
'user-agent': 'Mozilla/4.0 MDN Example',
'content-type': 'application/json'
},
method: 'POST', // 可选的值有*GET, POST, PUT, DELETE等。
mode: 'cors', // 可选的值有no-cors, cors, *same-origin
redirect: 'follow', // 可选的值有manual, *follow, error
referrer: 'no-referrer', // 可选的值有*client, no-referrer
})
.then(response => response.json()) // 解析JSON响应
}
(3)、fetch 方法的返回值
fetch 方法,总是返回一个包含响应结果的 Promise 对象。当该 Promise 对象为 resolve 状态时,在其回调函数中可获取 Response 对象。
Response 的可配置参数包括:
- type:类型,支持:basic,cors。
- url:请求地址。
- useFinalURL:Boolean 值,代表 url 是否是最终 URL。
- status:状态码 (例如:200,404等等)。
- ok:Boolean值,代表成功响应(status 值在 200-299 之间)。
- statusText:状态值(例如:OK)。
- headers:与响应相关联的 Headers 对象。
Response 提供的方法如下:
- clone():创建一个新的 Response 克隆对象。
- error():返回一个新的,与网络错误相关的 Response 对象。
- redirect():重定向,使用新的 URL 创建新的 response 对象。
- arrayBuffer():返回一个 promise,resolves 是一个 ArrayBuffer。
- blob():返回一个 promise,resolves 是一个 Blob。
- formData():返回一个 promise,resolves 是一个 FormData 对象。
- json():返回一个 promise,resolves 是一个 JSON 对象。
- text():返回一个 promise,resolves 是一个 USVString (text)。
3、检测 fetch() 请求是否成功
当 fetch 方法返回的 promise 对象的状态是 resolved 时,调用 then 方法,在 then 方法的回调函数中判断 response.ok 为 true 时,才表示 fetch() 请求是成功的。否则,fetch() 请求就是失败的。
fetch('flowers.jpg').then(function(response) {
if(response.ok) {
return response.blob();
}
throw new Error('Network response was not ok.');
}).then(function(myBlob) {
var objectURL = URL.createObjectURL(myBlob);
myImage.src = objectURL;
}).catch(function(error) {
console.log('There has been a problem with your fetch operation: ', error.message);
});
三、fetch 的应用
1、上传 JSON 数据
var url = 'https://example.com/profile';
var data = {username: 'example'};
fetch(url, {
method: 'POST', // 或者 'PUT'
body: JSON.stringify(data), // 数据可以是“string”或{object}!
headers: new Headers({
'Content-Type': 'application/json'
})
}).then(res => res.json())
.catch(error => console.error('Error:', error))
.then(response => console.log('Success:', response));
2、上传文件
可以通过 HTML <input type="file" /> 元素,FormData() 和 fetch() 上传文件。
var formData = new FormData();
var fileField = document.querySelector("input[type='file']");
formData.append('username', 'abc123');
formData.append('avatar', fileField.files[0]);
fetch('https://example.com/profile/avatar', {
method: 'PUT',
body: formData
})
.then(response => response.json())
.catch(error => console.error('Error:', error))
.then(response => console.log('Success:', response));
3、上传多个文件
可以通过HTML <input type="file" mutiple/> 元素,FormData() 和 fetch() 上传文件。
var formData = new FormData();
var photos = document.querySelector("input[type='file'][multiple]");
formData.append('title', 'My Vegas Vacation');
// formData 只接受文件、Blob 或字符串,不能直接传递数组,所以必须循环嵌入
for (let i = 0; i < photos.files.length; i++) {
formData.append('photo', photos.files[i]);
}
fetch('https://example.com/posts', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(response => console.log('Success:', JSON.stringify(response)))
.catch(error => console.error('Error:', error));
四、fetch 的实现
export default async(url = '', data = {}, type = 'GET', method = 'fetch') => {
type = type.toUpperCase();
url = baseUrl + url;
if (type == 'GET') {
let dataStr = ''; //数据拼接字符串
Object.keys(data).forEach(key => {
dataStr += key + '=' + data[key] + '&';
})
if (dataStr !== '') {
dataStr = dataStr.substr(0, dataStr.lastIndexOf('&'));
url = url + '?' + dataStr;
}
}
if (window.fetch && method == 'fetch') {
let requestConfig = {
credentials: 'include',//为了在当前域名内自动发送 cookie , 必须提供这个选项
method: type,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
mode: "cors",//请求的模式
cache: "force-cache"
}
if (type == 'POST') {
Object.defineProperty(requestConfig, 'body', {
value: JSON.stringify(data)
})
}
try {
const response = await fetch(url, requestConfig);
const responseJson = await response.json();
return responseJson
} catch (error) {
throw new Error(error)
}
} else {
return new Promise((resolve, reject) => {
let requestObj;
if (window.XMLHttpRequest) {
requestObj = new XMLHttpRequest();
} else {
requestObj = new ActiveXObject;
}
let sendData = '';
if (type == 'POST') {
sendData = JSON.stringify(data);
}
requestObj.open(type, url, true);
requestObj.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
requestObj.send(sendData);
requestObj.onreadystatechange = () => {
if (requestObj.readyState == 4) {
if (requestObj.status == 200) {
let obj = requestObj.response
if (typeof obj !== 'object') {
obj = JSON.parse(obj);
}
resolve(obj)
} else {
reject(requestObj)
}
}
}
})
}
}
五、fetch 的问题的解决
1、解决 fetch 的兼容问题
支持 fetch 的浏览器版本有:Chrome、Firefox、Safari 6.1+ 和 IE 10+。
虽然,不是所有的浏览器都支持 fetch 请求,但是我们可以用window.fetch polyfill来处理兼容问题。
2、解决 fetch 不支持 timeout 处理的问题
fetch 的 timeout 的特点:
- timeout 不是请求连接超时的含义,它表示请求的 response 时间(包括请求的连接、服务器处理 和 服务器响应回来的时间)。
- fetch 的 timeout 即使超时发生了,本次请求也不会被丢弃掉,它在后台仍然会发送到服务器端,只是本次请求的响应内容被丢弃而已。
(1)、通过手动控制 promise 状态的实例来实现 fetch 的 timeout 功能
实现 fetch 的 timeout 功能,其思想就是新创建一个可以手动控制promise状态的实例,根据不同情况来对新promise实例进行resolve或者reject,从而达到实现timeout的功能。
var oldFetchfn = fetch; //拦截原始的fetch方法
window.fetch = function(input, opts){//定义新的fetch方法,封装原有的fetch方法
return new Promise(function(resolve, reject){
var timeoutId = setTimeout(function(){
reject(new Error("fetch timeout"))
}, opts.timeout);
oldFetchfn(input, opts).then(
res=>{
clearTimeout(timeoutId);
resolve(res)
},
err=>{
clearTimeout(timeoutId);
reject(err)
}
)
})
}
(2)、利用 Promise.race 方法代替实现 fetch 的 timeout 的功能
var oldFetchfn = fetch; //拦截原始的fetch方法
window.fetch = function(input, opts){//定义新的fetch方法,封装原有的fetch方法
var fetchPromise = oldFetchfn(input, opts);
var timeoutPromise = new Promise(function(resolve, reject){
setTimeout(()=>{
reject(new Error("fetch timeout"))
}, opts.timeout)
});
retrun Promise.race([fetchPromise, timeoutPromise])
}
3、解决 fetch 不支持进度事件(Progress Event)
Progress Events定义了与客户端服务器通信有关的事件。这些事件最早其实只针对XHR操作,但目前也被其它API借鉴。有以下6个进度事件:
- loadstart:在接收到相应数据的第一个字节时触发。
- progress:在接收相应期间周期性地持续不断地触发。
- error:在请求发生错误时触发。
- abort:在因为调用abort()方法而终止链接时触发。
- load:在接收到完整的相应数据时触发。
- loadend:在通信完成或者触发error、abort或load事件后触发。
Ajax 的 XHR 是原生支持 progress 事件的,比如:
var xhr = new XMLHttpRequest()
xhr.open('POST', '/uploads')
xhr.onload = function() {}
xhr.onerror = function() {}
function updateProgress (event) {
if (event.lengthComputable) {
var percent = Math.round((event.loaded / event.total) * 100)
console.log(percent)
}
xhr.upload.onprogress =updateProgress; //上传的progress事件
xhr.onprogress = updateProgress; //下载的progress事件
}
xhr.send();
但 fetch 就不支持该事件。不过,fetch 内部设计实现了 Request 和 Response 类。其中 Response 封装一些方法和属性,通过 Response 实例可以访问这些方法和属性,例如 response.json()、response.body 等等。
response.body是一个可读字节流对象,其实现了一个getRender()方法,其具体作用是:用于读取响应的原始字节流,该字节流是可以循环读取的,直至body内容传输完成。因此,利用到这点可以模拟出 fetch 的 progress。
(1)、利用 response.body 模拟实现 fetch 的 progress 事件
// fetch() returns a promise that resolves once headers have been received
fetch(url).then(response => {
// response.body is a readable stream.
// Calling getReader() gives us exclusive access to the stream's content
var reader = response.body.getReader();
var bytesReceived = 0;
// read() returns a promise that resolves when a value has been received
reader.read().then(function processResult(result) {
// Result objects contain two properties:
// done - true if the stream has already given you all its data.
// value - some data. Always undefined when done is true.
if (result.done) {
console.log("Fetch complete");
return;
}
// result.value for fetch streams is a Uint8Array
bytesReceived += result.value.length;
console.log('Received', bytesReceived, 'bytes of data so far');
// Read some more, and call this function again
return reader.read().then(processResult);
});
});
(2)、使用 Promise+XHR 结合的方式实现类 fetch 的 progress 效果
function fetchProgress(url, opts={}, onProgress){
return new Promise(funciton(resolve, reject){
var xhr = new XMLHttpRequest();
xhr.open(opts.method || 'get', url);
for(var key in opts.headers || {}){
xhr.setRequestHeader(key, opts.headers[key]);
}
xhr.onload = e => resolve(e.target.responseText)
xhr.onerror = reject;
if (xhr.upload && onProgress){
xhr.upload.onprogress = onProgress; //上传
}
if ('onprogerss' in xhr && onProgress){
xhr.onprogress = onProgress; //下载
}
xhr.send(opts.body)
})
}
fetchProgress('/upload').then(console.log)
4、解决 fetch 不支持 JSONP 跨域的问题
JSONP 是外链一个javascript资源。
如何基于Promise来实现一个JSONP,并且使其看起来就像 fetch 支持 JSONP 一样?
可以使用 fetch-jsonp 插件。使用演示如下:
首先需要用 npm 安装 fetch-jsonp:
npm install fetch-jsonp --save-dev
然后在像下面一样使用:
fetchJsonp('/users.jsonp', {
timeout: 3000,
jsonpCallback: 'custom_callback'
})
.then(function(response) {
return response.json()
}).catch(function(ex) {
console.log('parsing failed', ex)
})
5、解决 fetch 的跨域问题
XHR 2 级 支持一种跨域:
- XHR 2 级是支持跨域请求的,只不过要满足浏览器端支持 CORS,服务器通过 Access-Control-Allow-Origin 来允许指定的源进行跨域,仅此一种方式。
fetch 支持两种跨域请求:
- 最常用:与 XHR 2 级一样,支持跨域请求需要满足两个条件:浏览器端支持 CORS 和 服务器通过 Access-Control-Allow-Origin 来允许指定的源进行跨域。
- 特殊:fetch 还支持一种跨域,不需要服务器支持的形式,具体可以通过其 mode 的配置项来实现。fetch 的 mode 配置项有3个值,如下:
- same-origin:该模式是不允许跨域的,它需要遵守同源策略,否则浏览器会返回一个error告知不能跨域;其对应的response type为basic。
- cors(最常用):该模式支持跨域请求,顾名思义它是以CORS的形式跨域;当然该模式也可以同域请求不需要后端额外的CORS支持;其对应的response type为cors。
- no-cors(特殊):该模式用于跨域请求但是服务器不带CORS响应头,也就是服务端不支持CORS;这也是fetch的特殊跨域请求方式;其对应的response type为opaque。(该模式允许浏览器发送本次跨域请求,但是不能访问响应返回的内容,这也是其response type为opaque透明的原因。)
总的来说,fetch 的跨域请求是使用 CORS 方式,需要浏览器和服务端的支持。
六、Ajax、Fetch、Axios 三者的区别
1、Ajax
Ajax:它的全称是:Asynchronous JavaScript And XML,翻译过来就是“异步的 Javascript 和 XML”。
Ajax 是一个技术统称,是一个概念模型,它囊括了很多技术,并不特指某一技术(比如XMLHttpRequest),它很重要的特性之一就是:局部刷新页面,无需重载整个页面。
Ajax 是一种思想,XMLHttpRequest 只是实现 Ajax 的一种方式。
比如:jQuery AJAX
$.ajax({
url: url,
method: 'GET', // 请求方法,可选项包括 GET、POST、PUT、DELETE 等
dataType: 'json',
headers: {
'Content-Type': 'application/json', // 请求头部信息
},
data: {
key1: value1,
key2: value2,
// 传递查询参数
},
success: function(data) {
// 处理返回的数据
},
error: function(xhr, status, error) {
// 处理请求错误
}
});
2、Fetch
Fetch 是在 ES6 出现的,它使用了 ES6 提出的 promise 对象。它是 XMLHttpRequest 的替代品。
Fetch 是一个 API,它是真实存在的,它是基于 promise 的。
很多小伙伴会把它与 Ajax 作比较,其实这是不对的,我们通常所说的 Ajax 是指使用 XMLHttpRequest 实现的 Ajax,所以真正应该和 XMLHttpRequest 作比较。
Fetch 的特点:
- 使用 promise,不使用回调函数。
- 采用模块化设计,比如 rep、res 等对象分散开来,比较友好。
- 通过数据流对象处理数据,可以提高网站性能。
fetch(url, {
method: 'GET', // 请求方法,可选项包括 GET、POST、PUT、DELETE 等
headers: {
'Content-Type': 'application/json', // 请求头部信息
},
body: JSON.stringify(data), // 请求体内容,通常用于 POST 或 PUT 请求
})
.then(response => response.json())
.then(data => {
// 处理返回的数据
})
.catch(error => {
// 处理请求错误
});
3、Axios
Axios 是随着 Vue 的兴起而被广泛使用的,目前来说,绝大多数的 Vue 项目中的网络请求都是利用 Axios 发起的。当然它并不是一个思想,或者一个原生 API,它是一个封装库。
Axios 是一个基于 promise 封装的网络请求库,它是基于 XHR 进行二次封装。
Axios 的特点:
- 从浏览器中创建 XMLHttpRequests
- 从 node.js 创建 http 请求
- 支持 Promise API
- 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换 JSON 数据
- 客户端支持防御 XSRF
import axios from 'axios';
axios.get(url, {
params: {
key1: value1,
key2: value2,
// 传递查询参数
},
headers: {
'Content-Type': 'application/json', // 请求头部信息
},
})
.then(response => {
// 处理返回的数据
})
.catch(error => {
// 处理请求错误
});
七、深入学习 fetch 的资源
Js中fetch方法:https://www.cnblogs.com/WindrunnerMax/p/13024711.html
传统 Ajax 已死,Fetch 永生:javascript - 传统 Ajax 已死,Fetch 永生 - 会影 - SegmentFault 思否
XHR or Fetch API ?:XHR or Fetch API ? - Jartto's blog
res.json() 与 res.send() 的区别?:Express中 res.json 和res.end 及res.send()_Andy____Li的博客-CSDN博客