面试的时候,经常会遇到要求应试者手写ajax,即使用原生JS代码自行构建XHR,然后绑上回调、传入参数,最后发送出去。
实际项目中,肯定不会使用原生XHR来发送ajax,因为写法还是功能都很简陋。工程中常见的几种ajax请求方式:使用jQuery框架的$.get( )$.ajax( )等,或者使用专用的ajax库例如axios等。随着前端技术的发展,浏览器现在给我们提供了FetchAPI,用于以简单优雅的方式发起浏览器原生支持的ajax请求。
本篇文章参考了网上的一些博文和MDN,另外可以在这里查看Fetch的兼容性。注意FetchAPI的兼容性较差,IE全线不支持,甚至一些现代浏览器也只有新版本支持。

1. fetch( )方法介绍

fetch( )方法由浏览器提供,它位于window对象中。
它接受2个参数,第一个参数表示请求的地址,第二个参数传入配置,可省略。
它的结果是Promise,对于fetch( )的不同执行情况:

  • 请求成功完成,触发resolve,将ajax取得的相应结果封装成一个Response作参数传入之;
  • 请求出错,触发reject,产生一个错误。

1.1. 参数

fetch方法的签名如下:

1
fetch(url [,init])

其中:
url参数表示请求资源的url,它也可以是一个Request对象;
init参数是一个可选的配置对象,它可以包含多条请求时的配置键值对。

init参数是一个对象,它可以设置以下配置:

  • method:请求发起的方式如POSTGET等,默认是GET
  • headers:请求头,是一个JSON或Header或URLSearchParams等,可以在这里控制请求头信息;
  • body:包含formdata等body内容。GET和HEAD类型请求是没有body的;
  • mode:跨域资源访问控制,值一般为same-origin或者cors,此项一般不需改动;
  • credentials:是否一同提交cookie;
  • cache:资源缓存策略;
  • redirect:重定向处理策略;
  • referrerPolicy:重定向时候设置HTTP头;
  • referrer:URL重定向的来源配置;
  • integrity:验证资源的hash。

一般情况下,只需要指定method;如果是Post请求,还可以指定body项;如果要控制请求头则配置headers,如果要发送cookie则配置credentials
如果显式指定了mode的值为same-origin,那么在跨域时请求将失败,未显式指定的时候是可以跨域请求的。

1.2. 详细配置

  • credentials凭证参数:

    • omit:默认值,不会提交任何cookie;
    • same-origin:同源时才会提交cookie;
    • include:同源和跨域情况都提交cookie。
  • mode模式:

    • cors:跨域模式,只暴露有限的头信息给服务器,但是body对服务器的开放的;
    • cors-with-forced-preflight:和cors类似,但是强制发起预检请求;
    • same-origin:仅同源,对于跨域请求会产生error;
    • no-cors:跨域但是不使用CORS流程的场合,例如CDN。此模式只能使用GETPOSTHEAD类型,Header无法更改,且Response的type设为opaque因此无法读取信息;
    • navigate:用于文档之间导航时创建导航请求时。
  • cache资源缓存策略:

    • default:默认值,对于有效的缓存直接使用,缓存若过期则会验证是否改动,并在有改动的时候更新缓存;
    • no-store:不检查不更新缓存,直接下载资源;
    • reload:不检查缓存,直接下载资源并更新缓存;
    • no-cache:类似default,但是即使缓存有效也会验证缓存;
    • force-cache:只要有资源就使用,不管过期与否;
    • only-if-cache:类似force-cache,但没有缓存时会产生error。仅在mode为same-origin时可用。
  • redirect:重定向处理策略:

    • follow:前往重定向的url;
    • error:产生一个错误;
    • manual:手动处理重定向。
  • referrer:URL重定向的来源配置:

    • no-referrer:不指定重定向来源url;
    • client:以浏览器上的url来作为来源地址;
    • 一个url字符串:指定一个url作为重定向的来源地址。
  • integrity验证参数:值形如sha1-xxxxxx...x,表示哈希方法和结果值,用于验证资源是否正确。

1.3. 方法返回

如果请求成功,返回值是一个Response对象被作为参数传入resolve回调内。
例如:

1
fetch('https://a-c.fun').then( t=>{ ... } );

以上代码中省略号所在的作用域内,箭头函数的参数t就是获取的Response对象,可以对其进行访问等操作。

Response对象有以下几个参数:

  • url:表示相应来源的url;
  • useFinalURL:为true或false表示是否是最终的url;
  • body:一个ReadableStream类型的对象,无法直接读取;
  • bodyUsed:表示body是否被使用过,因为它只能被读取一次;
  • headers:请求所关联的Headers对象;
  • ok:为true或false表示请求成功与否;
  • redirected:是否来自重定向,可能是一个列表;
  • status:状态码,200表示成功的响应;
  • statusText:状态码描述字符串;
  • type:类型,可其中一个值:
    • default:通过构造函数构造出的Response;
    • cors:CORS跨域,body可读,header的可读性取决于CORS访问设定;
    • basic:同域响应,除了Set-Cookie头其他都可用;
    • opaque:模式为no-cors时的相应,数据、相应状态都受到严格限制;
    • error:网络错误,大部分参数都无法使用。

如果想要读取body里的数据,也就是响应的内容,可以使用一下几个方法:

  • .text( ):以字符串方式处理,并构建一个Promise传入;
  • .json( ):以JSON方式来解析成对象,并构建一个Promise传入;
  • .blob( ):处理为Blob对象,它是无法更改的二进制对象,构建Promise传入;
  • .arrayBuffer( ):处理为ArrayBuffer对象并构建Promise传入;
  • .formData( ):处理为FormData对象,构建Promise并传入。

注意这里的几个方法全是返回一个Promise!
所以实际使用中一般会采用这种形式,例如获取API返回的文本:

1
fetch('https://a-c.fun').then( res=>res.text().then(t=>{...}) );

需要两个then才能获取到,上面代码中的省略号的作用域中,箭头函数的t参数就是传入的API返回的文本。

注意body只能被读取一次。
读取后bodyUsed会变为true,此时body便无法再被读取,因为它是一个ReadableStream类型。
如果想要重复使用body,可以使用以下代码:

1
2
3
4
let bak;
fetch('https://a-c.fun').then(res=>{
bak=res.clone();
});

Response对象有一个.clone( )方法,可以在它未被读取前创建一个克隆对象,此时bak就是克隆的body了。
注意必须在未被读取前克隆,如果是读取后,那么该Response便不可读,也无法克隆。

1.4. 与XHR方式的区别

  • fetch默认不提交Cookie,记得使用credentials,而XHR默认提交Cookie;
  • fetch不具备XHR的abort( )中断请求、不具备onprogress回调查看进度;
  • fetch在遇到4开头、5开头等状态码不会触发reject,而是抛出错误
  • fetch必须使用异步模式,不过可以使用await语法;
  • fetch读取一次数据后便无法再读取,必须使用clone( )拷贝。

2. Response对象

fetch( )在发送请求后返回一个Promise,而一个Response对象就被作为参数传入它的resolve回调中。

除此之外,还可以使用以下方式来创建Response对象:

  • new Response(body [,init]):构造函数来构建一个普通的Response对象:
    • body:可指定为FormDataBlobURLSearchParams等;
    • init:可选的配置参数,可以指定statusstatusTextheaders
  • Response.error( ):工厂构造一个网络错误类型的Response对象;
  • Response.redirect( ):工厂构造一个重定向的Response对象。

3. Request对象

Request对象的构造方法的参数和fetch( )的参数相同,都是这样:

1
new Request(url [,init])

它的第二个init配置参数还可以包含一些内容:
context:表示获取资源的类型,比如image。在fetch( )使用时该值为fetch。
这些参数一般用于涉及到ServiceWorker的代码中。

fetch( )的第一个参数可以直接传入一个Requset对象,此时将直接使用该Request的属性来发起请求。