目录
(1)、通过手动控制 promise 状态的实例来实现 fetch 的 timeout 功能
(2)、利用 Promise.race 方法代替实现 fetch 的 timeout 的功能
3、解决 fetch 不支持进度事件(Progress Event)
(1)、利用 response.body 模拟实现 fetch 的 progress 事件
(2)、使用 Promise+XHR 结合的方式实现类 fetch 的 progress 效果
fetch 是一种 HTTP 数据请求的方式,它不是 ajax 的进一步封装,而是 XMLHttpRequest(以下简称 XHR)的一种替代方案。
fetch 与 ajax 的区别:
fetch('http://example.com/movies.json')
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(myJson);
});
fetch 方法接受两个参数:一个 URL 地址或一个 request 对象 和 (可选的)一个配置项对象。
除了传给 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;
});
(可选的)一个配置项对象。该配置项包括所有对请求的设置。
配置项可选的参数有:
举个栗子:
// 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响应
}
fetch 方法,总是返回一个包含响应结果的 Promise 对象。当该 Promise 对象为 resolve 状态时,在其回调函数中可获取 Response 对象。
Response 的可配置参数包括:
Response 提供的方法如下:
当 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);
});
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));
可以通过 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));
可以通过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));
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 的浏览器版本有:Chrome、Firefox、Safari 6.1+ 和 IE 10+。
虽然,不是所有的浏览器都支持 fetch 请求,但是我们可以用window.fetch polyfill来处理兼容问题。
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)
}
)
})
}
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])
}
Progress Events定义了与客户端服务器通信有关的事件。这些事件最早其实只针对XHR操作,但目前也被其它API借鉴。有以下6个进度事件:
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。
// 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);
});
});
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)
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)
})
XHR 2 级 支持一种跨域:
fetch 支持两种跨域请求:
总的来说,fetch 的跨域请求是使用 CORS 方式,需要浏览器和服务端的支持。
Js中fetch方法:https://www.cnblogs.com/WindrunnerMax/p/13024711.html
传统 Ajax 已死,Fetch 永生:https://segmentfault.com/a/1190000003810652
XHR or Fetch API ?:http://jartto.wang/2017/01/17/xhr-or-fetch-api/
res.json() 与 res.send() 的区别?:https://andyli.blog.csdn.net/article/details/79753342