热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

fetch服务器不响应,fetch的常见问题及其解决办法

摘要:玩转fetch。Fundebug经授权转载,版权归原作者所有。首先声明一下,本文不是要讲解fetch的具体用法,不清楚

摘要: 玩转fetch。

Fundebug经授权转载,版权归原作者所有。

首先声明一下,本文不是要讲解fetch的具体用法,不清楚的可以参考MDN fetch教程。

引言

说道fetch就不得不提XMLHttpRequest了,XHR在发送web请求时需要开发者配置相关请求信息和成功后的回调,尽管开发者只关心请求成功后的业务处理,但是也要配置其他繁琐内容,导致配置和调用比较混乱,也不符合关注分离的原则;fetch的出现正是为了解决XHR存在的这些问题。例如下面代码:

fetch(url)

.then(function(response) {

return response.json();

})

.then(function(data) {

console.log(data);

})

.catch(function(e) {

console.log("Oops, error");

});

上面这段代码让开发者只关注请求成功后的业务逻辑处理,其他的不用关心,相当简单;也比较符合现代Promise形式,比较友好。

fetch是基于Promise设计的,从上面代码也能看得出来,这就要求fetch要配合Promise一起使用。正是这种设计,fetch所带来的优点正如传统 Ajax 已死,Fetch 永生总结的一样:

语法简单,更加语义化

基于标准的Promise实现,支持async/await

不过话说回来,fetch虽然有很多优点,但是使用fetch来进行项目开发时,也是有一些常见问题的,下面就来说说fetch使用的常见问题。

fetch兼容性

fetch是相对较新的技术,当然就会存在浏览器兼容性的问题,借用上面应用文章的一幅图加以说明fetch在各种浏览器的原生支持情况:

AAffA0nNPuCLAAAAAElFTkSuQmCC

从上图可以看出,在各个浏览器低版本的情况下都是不被支持的。

那么问题来了,如何在所有浏览器中通用fetch呢,当然就要考虑fetch的polyfill了。

上面说过,fetch是基于Promise来实现的,所以在低版本浏览器中Promise可能也未被原生支持,所以还需要Promise的polyfill;大多数情况下,实现fetch的polyfill需要涉及到的:

promise的polyfill,例如es6-promise、babel-polyfill提供的promise实现。

fetch的polyfill实现,例如isomorphic-fetch和whatwg-fetch

这样是否就可以安全的使用fetch来进行前后端通信了?上面说了在大多数情况下是这样,但是IE8/9则比较特殊:IE8它使用的是ES3,而IE9则对ES5部分支持。这种情况下还需要ES5的polyfill es5-shim支持了。

上述有关promise的polyfill实现,需要说明的是:

babel-runtime是不能作为Promise的polyfill的实现的,否则在IE8/9下使用fetch会报Promise未定义。为什么?我想大家猜到了,因为babel-runtime实现的polyfill是局部实现而不是全局实现,fetch底层实现用到Promise就是从全局中去取的,拿不到这报上述错误。

另外,顺便补充一下fetch的polyfill实现思路是:

首先判断浏览器是否原生支持fetch,否则结合Promise使用XMLHttpRequest的方式来实现;这正是whatwg-fetch的实现思路,而同构应用中使用的isomorphic-fetch,其客户端fetch的实现是直接require whatwg-fetch来实现的。

fetch默认不携带COOKIE

fetch发送请求默认是不发送COOKIE的,不管是同域还是跨域;那么问题就来了,对于那些需要权限验证的请求就可能无法正常获取数据,这时可以配置其credentials项,其有3个值:

omit: 默认值,忽略COOKIE的发送

same-origin: 表示COOKIE只能同域发送,不能跨域发送

include: COOKIE既可以同域发送,也可以跨域发送

credentials所表达的含义,其实与XHR2中的withCredentials属性类似,表示请求是否携带COOKIE;具体可以参考阮一峰老师的跨域资源共享 CORS 详解中withCredentials一节的介绍;

这样,若要fetch请求携带COOKIE信息,只需设置一下credentials选项即可,例如fetch(url, {credentials: 'include'});

另外补充一点:

fetch默认对服务端通过Set-COOKIE头设置的COOKIE也会忽略,若想选择接受来自服务端的COOKIE信息,也必须要配置credentials选项;

fetch请求对某些错误http状态不会reject

这主要是由fetch返回promise导致的,因为fetch返回的promise在某些错误的http状态下如400、500等不会reject,相反它会被resolve;只有网络错误会导致请求不能完成时,fetch 才会被 reject;所以一般会对fetch请求做一层封装,例如下面代码所示:

function checkStatus(response) {

if (response.status >&#61; 200 && response.status <300) {

return response;

}

const error &#61; new Error(response.statusText);

error.response &#61; response;

throw error;

}

function parseJSON(response) {

return response.json();

}

export default function request(url, options) {

let opt &#61; options || {};

return fetch(url, { credentials: "include", ...opt })

.then(checkStatus)

.then(parseJSON)

.then(data &#61;> data)

.catch(err &#61;> err);

}

fetch不支持超时timeout处理

用过fetch的都知道&#xff0c;fetch不像大多数ajax库那样对请求设置超时timeout&#xff0c;它没有有关请求超时的feature&#xff0c;这一点比较蛋疼。所以在fetch标准添加超时feature之前&#xff0c;都需要polyfill该特性。

实际上&#xff0c;我们真正需要的是abort()&#xff0c; timeout可以通过timeout&#43;abort方式来实现&#xff0c;起到真正超时丢弃当前的请求。

而在目前的fetch指导规范中&#xff0c;fetch并不是一个具体实例&#xff0c;而只是一个方法&#xff1b;其返回的promise实例根据Promise指导规范标准是不能abort的&#xff0c;也不能手动改变promise实例的状态&#xff0c;只能由内部来根据请求结果来改变promise的状态。

既然不能手动控制fetch方法执行后返回的promise实例状态&#xff0c;那么是不是可以创建一个可以手动控制状态的新Promise实例呢。所以&#xff1a;

实现fetch的timeout功能&#xff0c;其思想就是新创建一个可以手动控制promise状态的实例&#xff0c;根据不同情况来对新promise实例进行resolve或者reject&#xff0c;从而达到实现timeout的功能&#xff1b;

根据github上timeout handling上的讨论&#xff0c;目前可以有两种不同的解决方法&#xff1a;

方法一&#xff1a;单纯setTimeout方式

var oldFetchfn &#61; fetch; //拦截原始的fetch方法

window.fetch &#61; function(input, opts) {

//定义新的fetch方法&#xff0c;封装原有的fetch方法

return new Promise(function(resolve, reject) {

var timeoutId &#61; setTimeout(function() {

reject(new Error("fetch timeout"));

}, opts.timeout);

oldFetchfn(input, opts).then(

res &#61;> {

clearTimeout(timeoutId);

resolve(res);

},

err &#61;> {

clearTimeout(timeoutId);

reject(err);

}

);

});

};

当然在上面基础上可以模拟类似XHR的abort功能&#xff1a;

var oldFetchfn &#61; fetch;

window.fetch &#61; function(input, opts) {

return new Promise(function(resolve, reject) {

var abort_promise &#61; function() {

reject(new Error("fetch abort"));

};

var p &#61; oldFetchfn(input, opts).then(resolve, reject);

p.abort &#61; abort_promise;

return p;

});

};

方法二&#xff1a;利用Promise.race方法

Promise.race方法接受一个promise实例数组参数&#xff0c;表示多个promise实例中任何一个最先改变状态&#xff0c;那么race方法返回的promise实例状态就跟着改变&#xff0c;具体可以参考这里。

var oldFetchfn &#61; fetch; //拦截原始的fetch方法

window.fetch &#61; function(input, opts){//定义新的fetch方法&#xff0c;封装原有的fetch方法

var fetchPromise &#61; oldFetchfn(input, opts);

var timeoutPromise &#61; new Promise(function(resolve, reject){

setTimeout(()&#61;>{

reject(new Error("fetch timeout"))

}, opts.timeout)

});

retrun Promise.race([fetchPromise, timeoutPromise])

}

最后&#xff0c;对fetch的timeout的上述实现方式补充几点&#xff1a;

timeout不是请求连接超时的含义&#xff0c;它表示请求的response时间&#xff0c;包括请求的连接、服务器处理及服务器响应回来的时间&#xff1b;

fetch的timeout即使超时发生了&#xff0c;本次请求也不会被abort丢弃掉&#xff0c;它在后台仍然会发送到服务器端&#xff0c;只是本次请求的响应内容被丢弃而已&#xff1b;

fetch不支持JSONP

fetch是与服务器端进行异步交互的&#xff0c;而JSONP是外链一个Javascript资源&#xff0c;并不是真正ajax&#xff0c;所以fetch与JSONP没有什么直接关联&#xff0c;当然至少目前是不支持JSONP的。

这里我们把JSONP与fetch关联在一起有点差强人意&#xff0c;fetch只是一个ajax库&#xff0c;我们不可能使fetch支持JSONP&#xff1b;只是我们要实现一个JSONP&#xff0c;只不过这个JSONP的实现要与fetch的实现类似&#xff0c;即基于Promise来实现一个JSONP&#xff1b;而其外在表现给人感觉是fetch支持JSONP一样&#xff1b;

目前比较成熟的开源JSONP实现fetch-jsonp给我们提供了解决方案&#xff0c;想了解可以自行前往。不过再次想唠叨一下其JSONP的实现步骤&#xff0c;因为在本人面试的前端候选人中大部分人对JSONP的实现语焉不详&#xff1b;

使用它非常简单&#xff0c;首先需要用npm安装fetch-jsonp

npm install fetch-jsonp --save-dev

然后在像下面一样使用&#xff1a;

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事件的&#xff0c;例如下面代码这样&#xff1a;

var xhr &#61; new XMLHttpRequest();

xhr.open("POST", "/uploads");

xhr.onload &#61; function() {};

xhr.onerror &#61; function() {};

function updateProgress(event) {

if (event.lengthComputable) {

var percent &#61; Math.round((event.loaded / event.total) * 100);

console.log(percent);

}

xhr.upload.onprogress &#61; updateProgress; //上传的progress事件

xhr.onprogress &#61; updateProgress; //下载的progress事件

}

xhr.send();

但是fetch是不支持有关progress事件的&#xff1b;不过可喜的是&#xff0c;根据fetch的指导规范标准&#xff0c;其内部设计实现了Request和Response类&#xff1b;其中Response封装一些方法和属性&#xff0c;通过Response实例可以访问这些方法和属性&#xff0c;例如response.json()、response.body等等&#xff1b;

值得关注的地方是&#xff0c;response.body是一个可读字节流对象&#xff0c;其实现了一个getRender()方法&#xff0c;其具体作用是&#xff1a;

getRender()方法用于读取响应的原始字节流&#xff0c;该字节流是可以循环读取的&#xff0c;直至body内容传输完成&#xff1b;

因此&#xff0c;利用到这点可以模拟出fetch的progress&#xff0c;具体可以参考这篇文章2016 - the year of web streams。

// fetch() returns a promise that resolves once headers have been received

fetch(url).then(response &#61;> {

// response.body is a readable stream.

// Calling getReader() gives us exclusive access to the stream&#39;s content

var reader &#61; response.body.getReader();

var bytesReceived &#61; 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 &#43;&#61; 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);

});

});

另外&#xff0c;github上也有使用Promise&#43;XHR结合的方式实现类fetch的progress效果(当然这跟fetch完全不搭边)可以参考这里&#xff0c;具体代码如下&#xff1a;

function fetchProgress(url, opts&#61;{}, onProgress){

return new Promise(funciton(resolve, reject){

var xhr &#61; new XMLHttpRequest();

xhr.open(opts.method || &#39;get&#39;, url);

for(var key in opts.headers || {}){

xhr.setRequestHeader(key, opts.headers[key]);

}

xhr.onload &#61; e &#61;> resolve(e.target.responseText)

xhr.onerror &#61; reject;

if (xhr.upload && onProgress){

xhr.upload.onprogress &#61; onProgress; //上传

}

if (&#39;onprogerss&#39; in xhr && onProgress){

xhr.onprogress &#61; onProgress; //下载

}

xhr.send(opts.body)

})

}

fetchProgress(&#39;/upload&#39;).then(console.log)

fetch跨域问题

既然是ajax库&#xff0c;就不可避免与跨域扯上关系&#xff1b;XHR2是支持跨域请求的&#xff0c;只不过要满足浏览器端支持CORS&#xff0c;服务器通过Access-Control-Allow-Origin来允许指定的源进行跨域&#xff0c;仅此一种方式。

与XHR2一样&#xff0c;fetch也是支持跨域请求的&#xff0c;只不过其跨域请求做法与XHR2一样&#xff0c;需要客户端与服务端支持&#xff1b;另外&#xff0c;fetch还支持一种跨域&#xff0c;不需要服务器支持的形式&#xff0c;具体可以通过其mode的配置项来说明。

fetch的mode配置项有3个值&#xff0c;如下&#xff1a;

same-origin&#xff1a;该模式是不允许跨域的&#xff0c;它需要遵守同源策略&#xff0c;否则浏览器会返回一个error告知不能跨域&#xff1b;其对应的response type为basic。

cors: 该模式支持跨域请求&#xff0c;顾名思义它是以CORS的形式跨域&#xff1b;当然该模式也可以同域请求不需要后端额外的CORS支持&#xff1b;其对应的response type为cors。

no-cors: 该模式用于跨域请求但是服务器不带CORS响应头&#xff0c;也就是服务端不支持CORS&#xff1b;这也是fetch的特殊跨域请求方式&#xff1b;其对应的response type为opaque。

针对跨域请求&#xff0c;cors模式是常见跨域请求实现&#xff0c;但是fetch自带的no-cors跨域请求模式则较为陌生&#xff0c;该模式有一个比较明显的特点&#xff1a;

该模式允许浏览器发送本次跨域请求&#xff0c;但是不能访问响应返回的内容&#xff0c;这也是其response type为opaque透明的原因。

这与发送的请求类似&#xff0c;只是该模式不能访问响应的内容信息&#xff1b;但是它可以被其他APIs进行处理&#xff0c;例如ServiceWorker。另外&#xff0c;该模式返回的repsonse可以在Cache API中被存储起来以便后续的对它的使用&#xff0c;这点对script、css和图片的CDN资源是非常合适的&#xff0c;因为这些资源响应头中都没有CORS头。

总的来说&#xff0c;fetch的跨域请求是使用CORS方式&#xff0c;需要浏览器和服务端的支持。

参考文献



推荐阅读
  • 单页面应用 VS 多页面应用的区别和适用场景
    本文主要介绍了单页面应用(SPA)和多页面应用(MPA)的区别和适用场景。单页面应用只有一个主页面,所有内容都包含在主页面中,页面切换快但需要做相关的调优;多页面应用有多个独立的页面,每个页面都要加载相关资源,页面切换慢但适用于对SEO要求较高的应用。文章还提到了两者在资源加载、过渡动画、路由模式和数据传递方面的差异。 ... [详细]
  • 本文讨论了在使用PHP cURL发送POST请求时,请求体在node.js中没有定义的问题。作者尝试了多种解决方案,但仍然无法解决该问题。同时提供了当前PHP代码示例。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • 本文介绍了使用AJAX的POST请求实现数据修改功能的方法。通过ajax-post技术,可以实现在输入某个id后,通过ajax技术调用post.jsp修改具有该id记录的姓名的值。文章还提到了AJAX的概念和作用,以及使用async参数和open()方法的注意事项。同时强调了不推荐使用async=false的情况,并解释了JavaScript等待服务器响应的机制。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
    像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
  • WebSocket与Socket.io的理解
    WebSocketprotocol是HTML5一种新的协议。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送 ... [详细]
  • 本文介绍了一个React Native新手在尝试将数据发布到服务器时遇到的问题,以及他的React Native代码和服务器端代码。他使用fetch方法将数据发送到服务器,但无法在服务器端读取/获取发布的数据。 ... [详细]
  • 本文介绍了Java后台Jsonp处理方法及其应用场景。首先解释了Jsonp是一个非官方的协议,它允许在服务器端通过Script tags返回至客户端,并通过javascript callback的形式实现跨域访问。然后介绍了JSON系统开发方法,它是一种面向数据结构的分析和设计方法,以活动为中心,将一连串的活动顺序组合成一个完整的工作进程。接着给出了一个客户端示例代码,使用了jQuery的ajax方法请求一个Jsonp数据。 ... [详细]
  • ShiftLeft:将静态防护与运行时防护结合的持续性安全防护解决方案
    ShiftLeft公司是一家致力于将应用的静态防护和运行时防护与应用开发自动化工作流相结合以提升软件开发生命周期中的安全性的公司。传统的安全防护方式存在误报率高、人工成本高、耗时长等问题,而ShiftLeft提供的持续性安全防护解决方案能够解决这些问题。通过将下一代静态代码分析与应用开发自动化工作流中涉及的安全工具相结合,ShiftLeft帮助企业实现DevSecOps的安全部分,提供高效、准确的安全能力。 ... [详细]
  • Tomcat安装与配置教程及常见问题解决方法
    本文介绍了Tomcat的安装与配置教程,包括jdk版本的选择、域名解析、war文件的部署和访问、常见问题的解决方法等。其中涉及到的问题包括403问题、数据库连接问题、1130错误、2003错误、Java Runtime版本不兼容问题以及502错误等。最后还提到了项目的前后端连接代码的配置。通过本文的指导,读者可以顺利完成Tomcat的安装与配置,并解决常见的问题。 ... [详细]
author-avatar
手机用户2502899537
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有