五、Fetch API
1、基本用法
fetch()方法是暴露在全局作用域中的,包括主页面执行线程、模块和工作线程。
1)分派请求
fetch()只有一个必需的参数input 。多数情况下,这个参数是要获取资源的URL。
这个方法返回一个期约 。
1 2 let r = fetch ('/bar' ); console .log (r);
请求完成、资源可用时,期约会解决为一个Response对象 。
这个对象是API的封装,可以通过它取得相应资源。获取资源要使用这个对象的属性和方法 ,掌握响应的情况并将负载转换为有用的形式。
1 2 3 4 5 fetch ('bar.txt' ) .then ((response ) => { console .log (response); });
2、读取响应
读取响应内容的最简单方式是取得纯文本格式的内容,这要用到text()方法。
这个方法返回一个期约,会解决为取得资源的完整内容。
1 2 3 4 5 6 7 fetch ('bar.txt' ) .then ((response ) => { response.text ().then ((data ) => { console .log (data); }); });
内容的结构通常是打平的:1 2 3 4 fetch ('bar.txt' ) .then ((response ) => response.text ()) .then ((data ) => console .log (data));
3)处理状态码和请求失败
Fetch API支持通过Response的status ( 状态码)和statusText (状态文本)属性检查响应状态。
成功获取响应的请求通常会产生值为200的状态码 。
请求不存在的资源通常会产生值为 404 的状态码 。
请求的 URL 如果抛出服务器错误会产生值为 500 的状态码 。
只要服务器返回了响应,fetch()期约都会解决。
1 2 3 4 5 fetch ('/bar' ) .then ((response ) => { console .log (response.status ); console .log (response.statusText ); });
通常状态码为200时就会被认为成功了,其他情况可以被认为未成功。为区分这两种情况,可以在状态码非200-299时检查Response对象的ok属性。
1 2 3 4 5 6 7 8 9 10 fetch ('/bar' ) .then ((response ) => { console .log (response.status ); console .log (response.ok ); }); fetch ('/does-not-exist' ) .then ((response ) => { console .log (response.status ); console .log (response.ok ); });
违反CORS、无网络连接、HTTPS错配及其他浏览器/网络策略问题都会导致期约被拒绝 。
可以通过url属性检查通过fetch()发送请求时使用的完整URL。
1 2 3 4 5 6 7 8 9 10 console .log (window .location .href ); fetch ('qux' ).then ((response ) => console .log (response.url )); fetch ('/qux' ).then ((response ) => console .log (response.url )); fetch ('//qux.com' ).then ((response ) => console .log (response.url )); fetch ('https://qux.com' ).then ((response ) => console .log (response.url ));
4)自定义选项
只使用URL时,fetch()会发送GET请求,只包含最低限度的请求头。
要进一步配置如何发送请求,需要传入可选的第二个参数init对象。
2、常见Fetch请求模式 1)发送JSON数据 1 2 3 4 5 6 7 8 9 10 11 let payload = JSON .stringify ({ foo : 'bar' }); let jsonHeaders = new Headers ({ 'Content-Type' : 'application/json' }); fetch ('/send-me-json' , { method : 'POST' , body : payload, headers : jsonHeaders });
2)在请求体中发送参数 1 2 3 4 5 6 7 8 9 let payload = 'foo=bar&baz=qux' ; let paramHeaders = new Headers ({ 'Content-Type' : 'application/x-www-form-urlencoded; charset=UTF-8' }); fetch ('/send-me-params' , { method : 'POST' , body : payload, headers : paramHeaders });
3)发送文件
因为请求体支持FormData实现,所以fetch()也可以序列化并发送文件字段中的文件。
1 2 3 4 5 6 7 let imageFormData = new FormData (); let imageInput = document .querySelector ("input[type='file']" ); imageFormData.append ('image' , imageInput.files [0 ]); fetch ('/img-upload' , { method : 'POST' , body : imageFormData });
4)加载Blob文件
Fetch API也能提供Blob类型的响应,而Blob又可以兼容多种浏览器API。
可以使用响应对象上暴露的blob()方法。
方法返回一个期约,解决为一个Blob的实例。可以将这个实例传给URL.create0bjectUrl()以生成可以添加给图片元素src属性的值。
1 2 3 4 5 6 const imageElement = document .querySelector ('img' ); fetch ('my-image.png' ) .then ((response ) => response.blob ()) .then ((blob ) => { imageElement.src = URL .createObjectURL (blob); });
5)发送跨源请求
从不同的源请求资源,响应要包含CORS头部才能保证浏览器收到响应。没有这些头部,跨源请求会失败并抛出错误。
1 2 3 fetch ('//cross-origin.com' );
如果代码不需要访问响应,也可以发送no-cors请求。此时响应的type属性值为opaque,因此无法读取相应内容。这种方式适合发送探测请求或者将响应缓存起来供以后使用。
1 2 3 fetch ('//cross-origin.com' , { method : 'no-cors' }) .then ((response ) => console .log (response.type ));
6)中断请求
Fetch API支持通过AbortController/AbortSignal 对中断请求。
调用AbortController.abort()会中断所有网络传输,特别适合希望停止传输大型负载的情况。
中断进行中的fetch()请求会导致包含错误的拒绝。
1 2 3 4 5 6 let abortController = new AbortController (); fetch ('wikipedia.zip' , { signal : abortController.signal }) .catch (() => console .log ('aborted!' ); setTimeout (() => abortController.abort (), 10 );
Headers对象是所有外发请求和入站响应头部的容器 。
每个外发的Request实例都包含一个空的Headers实例,可以通过Request.prototype.headers 访问,每个入站Response实例也可以通过Response.prototype.headers访问包含着响应头部的Headers对象。这两个属性都是可修改属性 。
使用new Headers()也可以创建一个新实例。
因为HTTP头部本质上是序列化后的键/值对,它们的JavaScript表示则是中间接口 。
Headers与Map类型都有get()、set()、has()和delete()等实例方法。
Headers和Map都可以使用一个可迭代对象来初始化。
都有相同的keys()、values()和entries()迭代器接口。
1、 在初始化Headers对象时,可以使用键/值对形式的对象,而Map不可以。 2、 Headers对象通过append()方法支持添加多个值。在Headers实例中还不存在的头部上调用append()方法相当于调用set ()。
1 2 3 4 5 6 7 8 9 10 11 let seed = {foo : 'bar' }; let h = new Headers (seed); console .log (h.get ('foo' )); let m = new Map (seed); let h = new Headers (); h.append ('foo' , 'bar' ); console .log (h.get ('foo' )); h.append ('foo' , 'baz' ); console .log (h.get ('foo' ));
3)头部护卫
Headers对象使用护卫来防止不被允许的修改。
不同的护卫设置会改变set()、append()和 delete()的行为。违反护卫限制会抛出TypeError。
4、Request对象
Request对象是获取资源请求的接口。这个接口暴露了请求的相关信息,也暴露了使用请求体的不同方式。
1)创建Request对象
通过构造函数初始化Request对象,为此需要传入一个input参数,一般是URL。
1 2 3 let r = new Request ('https://foo.com' ); console .log (r);
Request构造函数也接收第二个参数一个 init 对象。
没有在init对象中涉及的值则会使用默认值。
2)克隆Request对象
Fetch API提供了两种不太一样的方式用于创建Request对象的副本:
使用Request构造函数;
使用clone()方法。
将Request实例作为input参数传给Request构造函数,会得到该请求的一个副本:
1 2 3 let r1 = new Request ('https://foo.com' ); let r2 = new Request (r1); console .log (r2.url );
如果再传入init对象,则init对象的值会覆盖源对象中同名的值:
1 2 3 4 let r1 = new Request ('https://foo.com' ); let r2 = new Request (r1, {method : 'POST' }); console .log (r1.method ); console .log (r2.method );
这种克隆方式并不总能得到一模一样的副本 。最明显的是,第一个请求的请求体会被标记为“已使用”:
1 2 3 4 let r1 = new Request ('https://foo.com' , { method : 'POST' , body : 'foobar' }); let r2 = new Request (r1); console .log (r1.bodyUsed ); console .log (r2.bodyUsed );
第二种克隆Request对象的方式是使用clone()方法,这个方法会创建一模一样的副本,任何值都不会被覆盖。
与第一种方式不同,这种方法不会将任何请求的请求体标记为“已使用”。
如果请求对象的bodyUsed属性为true (即请求体已被读取),那么上述任何一种方式都不能用来创建这个对象的副本 。
1 2 3 4 5 6 let r1 = new Request ('https://foo.com' , { method : 'POST' , body : 'foobar' }); let r2 = r1.clone (); console .log (r1.url ); console .log (r2.url ); console .log (r1.bodyUsed ); console .log (r2.bodyUsed );
3)在fetch()中使用Request对象
在调用fetch()时,可以传入已经创建好的Request实例而不是URL。
fetch()会在内部克隆传入的Requeat对象。
fetch()也不能拿请求体已经用过的Request对象来发送请求。
有请求体的Request只能在一次fetch中使用 。
1 2 3 4 5 let r = new Request ('https://foo.com' ); fetch (r); fetch (r, { method : 'POST' });
要想基于包含请求体的相同Request对象多次调用fetch(),必须在第一次发送fetch()请求前调用clone()。
1 2 3 4 5 let r = new Request ('https://foo.com' , { method : 'POST' , body : 'foobar' }); fetch (r.clone ()); fetch (r.clone ()); fetch (r);
5、Response对象
Response 对象是获取资源响应的接口 。
这个接口暴露了响应的相关信息,也暴露了使用响应体的不同方式 。
1)创建Response对象
可以通过构造函数初始化Response对象且不需要参数。
此时响应实例的属性均为默认值,因为它并不代表实际的HTTP响应。
1 2 3 4 5 6 7 8 9 10 11 12 13 let r = new Response (); console .log (r);
Response构造函数接收一个可选的body参数。
这个body可以是null,等同于fetch()参数init中的body。
还可以接收一个可选的init对象,这个对象可以包含下表所列的键和值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 let r = new Response ('foobar' , { status : 418 , statusText : 'I\'m a teapot' }); console .log (r);
**大多数情况下,产生Response 对象的主要方式是调用fetch()**, 它返回一个最后会解决为Response对象的期约,这个Response 对象代表实际的HTTP响应。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 fetch ('https://foo.com' ) .then ((response ) => { console .log (response); });
Response类还有两个用于生成Response对象的静态方法: Response.redirect()和Response.error()。
前者接收一个URL和一个重定向状态码(301、302、303、307或308),返回重定向的Response对象。
提供的状态码必须对应重定向,否则会抛出错误。
另一个静态方法Response.error()用于产生表示网络错误的Response 对象。
1 2 3 4 5 6 7 8 9 10 11 12 console .log (Response .redirect ('https://foo.com' , 301 ));
2)读取响应状态信息
Response对象包含一组只读属性,描述了请求完成后的状态,如下表所示。
以下代码演示了返回200、302、404和500状态码的URL对应的响应:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 fetch ('//foo.com' ).then (console .log ); fetch ('//foo.com/redirect-me' ).then (console .log ); fetch ('//foo.com/does-not-exist' ).then (console .log ); fetch ('//foo.com/throws-error' ).then (console .log );
3)克隆Response对象
克隆Response对象的主要方式是使用clone()方法,这个方法会创建一个一模一样的副本,不会覆盖任何值。这样不会将任何请求的请求体标记为已使用。
1 2 3 4 let r1 = new Response ('foobar' ); let r2 = r1.clone (); console .log (r1.bodyUsed ); console .log (r2.bodyUsed );
如果响应对象的bodyUsed属性为true (即响应体已被读取),则不能再创建这个对象的副本。在响应体被读取之后再克隆会导致抛出TypeError。
1 2 3 4 5 6 let r = new Response ('foobar' ); r.clone (); r.text (); r.clone ();
1 2 3 4 let r = new Response ('foobar' ); r.text ().then (console .log ); r.text ().then (console .log );
要多次读取包含响应体的同一个Response对象,必须在第一次读取前调用clone():
1 2 3 4 let r = new Response ('foobar' ); r.clone ().text ().then (console .log ); r.clone ().text ().then (console .log ); r.text ().then (console .log );
6、Request、Response及Body混入 1)Body.text()
Body.text ()方法返回期约,解决为将缓冲区转存得到的UTF-8 格式字符串。下面的代码展示了在Response对象上使用Body.text():
1 2 3 4 5 6 7 fetch ('https://foo.com' ) .then ((response ) => response.text ()) .then (console .log );
以下代码展示了在Request对象上使用Body.text():
1 2 3 4 let request = new Request ('https://foo.com' , { method : 'POST' , body : 'barbazqux' }); request.text () .then (console .log );
2)Body.json()
Body.json()方法返回期约,解决为将缓冲区转存得到的JSON。下面的代码展示了在Response对象上使用Body.json():
1 2 3 4 fetch ('https://foo.com/foo.json' ) .then ((response ) => response.json ()) .then (console .log );
以下代码展示了在Request对象上使用Body.json():
1 2 3 let request = new Request ('https://foo.com' , { method :'POST' , body : JSON .stringify ({ bar : 'baz' }) }); request.json ().then (console .log );
3)Body.formData()
浏览器可以将FormData对象序列化/反序列化为主体。
1 2 let myFormData = new FormData (); myFormData.append ('foo' , 'bar' );
Body.formData()方法返回期约,解决为将缓冲区转存得到的FormData实例。
下面的代码展示了在Response对象上使用Body.formData():
1 2 3 4 fetch ('https://foo.com/form-data' ) .then ((response ) => response.formData ()) .then ((formData ) => console .log (formData.get ('foo' ));
以下代码展示了在Request对象上使用Body.formData():
1 2 3 4 5 let myFormData = new FormData (); myFormData.append ('foo' , 'bar' ); let request = new Request ('https://foo.com' , { method :'POST' , body : myFormData }); request.formData () .then ((formData ) => console .log (formData.get ('foo' ));
4)Body.arrayBuffer()
可以使用Body.arrayBuffer()将主体内容转换为ArrayBuffer实例。
Body.arrayBuffer()方法返回期约,解决为将缓冲区转存得到的ArrayBuffer实例。
下面的代码展示了在Response对象上使用Body.arrayBuffer():
1 2 3 4 fetch ('https://foo.com' ) .then ((response ) => response.arrayBuffer ()) .then (console .log );
以下代码展示了在Request对象上使用Body.arrayBuffer():
1 2 3 4 5 let request = new Request ('https://foo.com' , { method :'POST' , body : 'abcdefg' }); request.arrayBuffer () .then ((buf ) => console .log (new Int8Array (buf)));
5)Body.blob()
有时候,可能需要以原始二进制格式使用主体,不用查看和修改。
可以使用Body.blob()将主体内容转换为Blob实例。
Body.blob()方法返回期约,解决为将缓冲区转存得到的Blob实例 。
下面的代码展示了在Response对象上使用Body.blob():
1 2 3 4 fetch ('https://foo.com' ) .then ((response ) => response.blob ()) .then (console .log );
以下代码展示了在Request对象上使用Body.blob():
1 2 3 let request = new Request ('https://foo.com' , { method :'POST' , body : 'abcdefg' }); request.blob ().then (console .log );
6)一次性流
因为Body混入是构建在ReadableStream之上的,所以主体流只能使用一次 。
这意味着所有主体混入方法都只能调用一次,再次调用就会抛出错误。
1 2 3 4 5 6 fetch ('https://foo.com' ) .then ((response ) => response.blob ().then (() => response.blob ())); let request = new Request ('https://foo.com' , { method : 'POST' , body : 'foobar' }); request.blob ().then (() => request.blob ());
即使是在读取流的过程中,所有这些方法也会在它们被调用时给ReadableStream加锁,以阻止其他读取器访问:
1 2 3 4 5 6 7 8 9 10 fetch ('https://foo.com' ) .then ((response ) => { response.blob (); response.blob (); }); let request = new Request ('https://foo.com' , { method : 'POST' , body : 'foobar' }); request.blob (); request.blob ();