共计 3983 个字符,预计需要花费 10 分钟才能阅读完成。
首先声明一下,本文不是要讲解 fetch 的具体用法,不清楚的可以参考 MDN fetch 教程。
fetch 默认不携带 cookie
配置其 credentials 项,其有 3 个值:
- omit: 默认值,忽略 cookie 的发送
- same-origin: 表示 cookie 只能同域发送,不能跨域发送
- include: cookie 既可以同域发送,也可以跨域发送
credentials 所表达的含义,其实与 XHR2 中的 withCredentials 属性类似,表示请求是否携带 cookie
这样,若要 fetch 请求携带 cookie 信息,只需设置一下 credentials 选项即可,例如 fetch(url, {credentials: ‘include’});
fetch 请求对某些错误 http 状态不会 reject
这主要是由 fetch 返回 promise 导致的,因为 fetch 返回的 promise 在某些错误的 http 状态下如 400、500 等不会 reject,相反它会被 resolve;只有网络错误会导致请求不能完成时,fetch 才会被 reject;所以一般会对 fetch 请求做一层封装,例如下面代码所示:
function checkStatus(response) {if (response.status >= 200 && response.status < 300) {return response;}
const error = new Error(response.statusText);
error.response = response;
throw error;
}
function parseJSON(response) {return response.json();
}
export default function request(url, options) {let opt = options||{};
return fetch(url, {credentials: 'include', ...opt})
.then(checkStatus)
.then(parseJSON)
.then((data) => (data))
.catch((err) => (err));
}
fetch 不支持超时 timeout 处理
方法一:单纯 setTimeout 方式
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)
}
)
})
}
当然在上面基础上可以模拟类似 XHR 的 abort 功能:
var oldFetchfn = fetch;
window.fetch = function(input, opts){return new Promise(function(resolve, reject){var abort_promise = function(){reject(new Error("fetch abort"))
};
var p = oldFetchfn(input, opts).then(resolve, reject);
p.abort = abort_promise;
return p;
})
}
方法二:利用 Promise.race 方法
Promise.race 方法接受一个 promise 实例数组参数,表示多个 promise 实例中任何一个最先改变状态,那么 race 方法返回的 promise 实例状态就跟着改变,具体可以参考这里。
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])
}
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)
})
fetch 不支持 progress 事件
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 是不支持有关 progress 事件的;不过可喜的是,根据 fetch 的指导规范标准,其内部设计实现了 Request 和 Response 类;其中 Response 封装一些方法和属性,通过 Response 实例可以访问这些方法和属性,例如 response.json()、response.body 等等;
值得关注的地方是,response.body 是一个可读字节流对象,其实现了一个 getRender() 方法,其具体作用是:
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);
});
});
fetch 跨域问题
fetch 的 mode 配置项有 3 个值,如下:
- same-origin:该模式是不允许跨域的,它需要遵守同源策略,否则浏览器会返回一个 error 告知不能跨域;其对应的 response type 为 basic。
- cors: 该模式支持跨域请求,顾名思义它是以 CORS 的形式跨域;当然该模式也可以同域请求不需要后端额外的 CORS 支持;其对应的 response type 为 cors。
- no-cors: 该模式用于跨域请求但是服务器不带 CORS 响应头,也就是服务端不支持 CORS;这也是 fetch 的特殊跨域请求方式;其对应的 response type 为 opaque。
参考
- MDN-ReadableStream
- MDN fetch
- https://segmentfault.com/a/1190000008484070#articleHeader3
友情链接
- 前端知识体系
- react 服务端渲染博客