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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
|
---
title: Using Fetch
slug: Web/API/Fetch_API/Using_Fetch
tags:
- Fetch
- HTTP
- Promise
- Response
- request
translation_of: Web/API/Fetch_API/Using_Fetch
---
<p>{{DefaultAPISidebar("Fetch API")}}</p>
<div class="summary">
<p><a href="/en-US/docs/Web/API/Fetch_API">Fetch API</a> 提供了一種 JavaScript Interface 來操作 HTTP pipeline,比方 request 和 response。同時它也提供了 global 的 {{domxref("GlobalFetch.fetch","fetch()")}} method,使得在網路上非同步地 fetch resources 這件事變得簡單易懂。</p>
</div>
<p>同樣的功能,以前都是使用 {{domxref("XMLHttpRequest")}},而 Fetch 作為其替代方案,能更方便地整合在如 {{domxref("ServiceWorker_API", "Service Workers")}} 等相關技術上。此外,Fetch 具備額外的 logical palce,能拿來定義其他和 HTTP 有關的東西,像是 CORS 和 HTTP extensions。</p>
<p> <code>fetch</code> 和 <code>jQuery.ajax()</code> 有三個主要的差異:</p>
<ul>
<li><code>fetch()</code> 回傳的 promise <strong>不會 reject HTTP 的 error status</strong>,就算是 HTTP 404 或 500 也一樣。相反地,它會正常地 resolve,並把 <code>ok</code> status 設為 false。會讓它發生 reject 的只有網路錯誤或其他會中斷 request 的情況。</li>
<li><code>fetch</code> <strong>可以接收跨站的 cookies</strong>,你可以用 Fetch 來建立跨站的 session。</li>
<li><code>fetch</code> <strong>不會傳送 cookies</strong>,除非你有設定 credentials 的 <a href="/zh-TW/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters">init option</a>。 (Since <a href="https://github.com/whatwg/fetch/pull/585" rel="nofollow noopener">Aug 25, 2017</a>. The spec changed the default credentials policy to <code>same-origin</code>. Firefox changed since 61.0b13.)</li>
</ul>
<h2 id="使用_Fetch_發送請求_request">使用 Fetch 發送請求 ( request )</h2>
<p>用法簡單,如下:</p>
<pre class="brush: js notranslate">fetch('http://example.com/movies.json')
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(myJson);
});
</pre>
<p>這裡要使用 fetch 透過網路取得 json 然後印出在 console,最簡單的方式只需要一個參數就是資料的 URI,fetch 會回傳一個包含 response 的 promise 。</p>
<p>這個範例使用的 url 只是示意用。</p>
<p>回傳的 response 需要透過 {{domxref("Body.json","json()")}} (在 {{domxref("Body")}} 可以找到定義, Body 是用 {{domxref("Request")}} 和 {{domxref("Response")}} 實作出來的物件.)</p>
<div class="note">
<p><strong>備註</strong>: 其實 Body 還提供了其他類似的功能可以將內容輸成其他類型格式,詳見{{anch("Body")}} </p>
</div>
<p>Fetch 請求的安全性 <a href="/en-US/docs/Security/CSP/CSP_policy_directives">Content Security Policy</a>(內容安全策略) 是由 header 中的 <code>connect-src</code> directive 所設定 ,並非其他 directive ( 比如:img-src、default-src 等)。</p>
<h3 id="Request_可用的設定值">Request 可用的設定值</h3>
<p><code>fetch()</code> 第二個參數是選用的,可以傳送一個 <code>init</code> Object 來設定 request。</p>
<p>更多可以用的設定值詳見 {{domxref("GlobalFetch.fetch","fetch()")}} </p>
<pre class="brush: js notranslate">// 來發個 POST Request:
postData('http://example.com/answer', {answer: 42})
.then(data => console.log(data)) // JSON from `response.json()` call
.catch(error => console.error(error))
function postData(url, data) {
// Default options are marked with *
return fetch(url, {
body: JSON.stringify(data), // must match 'Content-Type' header
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, etc.
mode: 'cors', // no-cors, cors, *same-origin
redirect: 'follow', // manual, *follow, error
referrer: 'no-referrer', // *client, no-referrer
})
.then(response => response.json()) // 輸出成 json
}
</pre>
<h3 id="包含憑證Credentials_的_Request_用法">包含憑證(Credentials) 的 Request 用法</h3>
<p>要讓瀏覽器將 credentials 跟著 request 一起送出, 方式就是在 <code>init</code> object 加上 <code>credentials: 'include'</code> </p>
<pre class="brush: js notranslate">fetch('https://example.com', {
credentials: 'include'
})</pre>
<p>如果只想要把 credentials 發送給同源的 URL ,加上<code>credentials: 'same-origin'</code>。</p>
<pre class="brush: js notranslate">// The calling script is on the origin 'https://example.com'
fetch('https://example.com', {
credentials: 'same-origin'
})</pre>
<p>或要確保瀏覽器不會帶著 credentials 請求,可以用 <code>credentials: 'omit'</code> 。</p>
<pre class="brush: js notranslate">fetch('https://example.com', {
credentials: 'omit'
})</pre>
<h3 id="上傳JSON資料">上傳JSON資料</h3>
<p>使用 {{domxref("GlobalFetch.fetch","fetch()")}} 來 POST JSON 格式的資料。</p>
<pre class="brush: js notranslate">var url = 'https://example.com/profile';
var data = {username: 'example'};
fetch(url, {
method: 'POST', // or 'PUT'
body: JSON.stringify(data), // data can be `string` or {object}!
headers: new Headers({
'Content-Type': 'application/json'
})
}).then(res => res.json())
.catch(error => console.error('Error:', error))
.then(response => console.log('Success:', response));
</pre>
<h3 id="上傳檔案">上傳檔案</h3>
<p>上傳檔案可以透過使用HTML <code><input type="file" /></code> input element, {{domxref("FormData.FormData","FormData()")}} 與{{domxref("GlobalFetch.fetch","fetch()")}}.</p>
<pre class="brush: js notranslate">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));
</pre>
<h3 id="如何確認fetch是否成功">如何確認fetch是否成功</h3>
<p>當{{domxref("GlobalFetch.fetch","fetch()")}}遇到CORS或server設定錯誤導致network error時, promise會reject並附上{{jsxref("TypeError")}}的回應, 但在權限或類似問題導致404的常見狀況下, 卻不會導致network error.</p>
<p>因此, 確認<code>fetch()</code>是否成功的正確方式, 應包含檢查promise resolved, 以及檢查{{domxref("Response.ok")}}的屬性是否為true. 代碼如下例:</p>
<pre class="brush: js notranslate">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);
});</pre>
<h3 id="Supplying_your_own_request_object">Supplying your own request object</h3>
<p>Instead of passing a path to the resource you want to request into the <code>fetch()</code> call, you can create a request object using the {{domxref("Request.Request","Request()")}} constructor, and pass that in as a <code>fetch()</code> method argument:</p>
<pre class="brush: js notranslate">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;
});</pre>
<p><code>Request()</code> accepts exactly the same parameters as the <code>fetch()</code> method. You can even pass in an existing request object to create a copy of it:</p>
<pre class="brush: js notranslate">var anotherRequest = new Request(myRequest, myInit);</pre>
<p>This is pretty useful, as request and response bodies are one use only. Making a copy like this allows you to make use of the request/response again, while varying the <code>init</code> options if desired. The copy must be made before the body is read, and reading the body in the copy will also mark it as read in the original request.</p>
<div class="note">
<p><strong>Note</strong>: There is also a {{domxref("Request.clone","clone()")}} method that creates a copy. Both methods of creating a copy will fail if the body of the original request or response has already been read, but reading the body of a cloned response or request will not cause it to be marked as read in the original.</p>
</div>
<h2 id="Headers">Headers</h2>
<p>The {{domxref("Headers")}} interface allows you to create your own headers object via the {{domxref("Headers.Headers","Headers()")}} constructor. A headers object is a simple multi-map of names to values:</p>
<pre class="brush: js notranslate">var content = "Hello World";
var myHeaders = new Headers();
myHeaders.append("Content-Type", "text/plain");
myHeaders.append("Content-Length", content.length.toString());
myHeaders.append("X-Custom-Header", "ProcessThisImmediately");</pre>
<p>The same can be achieved by passing an array of arrays or an object literal to the constructor:</p>
<pre class="brush: js notranslate">myHeaders = new Headers({
"Content-Type": "text/plain",
"Content-Length": content.length.toString(),
"X-Custom-Header": "ProcessThisImmediately",
});</pre>
<p>The contents can be queried and retrieved:</p>
<pre class="brush: js notranslate">console.log(myHeaders.has("Content-Type")); // true
console.log(myHeaders.has("Set-Cookie")); // false
myHeaders.set("Content-Type", "text/html");
myHeaders.append("X-Custom-Header", "AnotherValue");
console.log(myHeaders.get("Content-Length")); // 11
console.log(myHeaders.get("X-Custom-Header")); // ["ProcessThisImmediately", "AnotherValue"]
myHeaders.delete("X-Custom-Header");
console.log(myHeaders.get("X-Custom-Header")); // [ ]</pre>
<p>Some of these operations are only useful in {{domxref("ServiceWorker_API","ServiceWorkers")}}, but they provide a much nicer API for manipulating headers.</p>
<p>All of the Headers methods throw a <code>TypeError</code> if a header name is used that is not a valid HTTP Header name. The mutation operations will throw a <code>TypeError</code> if there is an immutable guard (see below). Otherwise they fail silently. For example:</p>
<pre class="brush: js notranslate">var myResponse = Response.error();
try {
myResponse.headers.set("Origin", "http://mybank.com");
} catch(e) {
console.log("Cannot pretend to be a bank!");
}</pre>
<p>A good use case for headers is checking whether the content type is correct before you process it further. For example:</p>
<pre class="brush: js notranslate">fetch(myRequest).then(function(response) {
var contentType = response.headers.get("content-type");
if(contentType && contentType.includes("application/json")) {
return response.json();
}
throw new TypeError("Oops, we haven't got JSON!");
})
.then(function(json) { /* process your JSON further */ })
.catch(function(error) { console.log(error); });</pre>
<h3 id="Guard">Guard</h3>
<p>Since headers can be sent in requests and received in responses, and have various limitations about what information can and should be mutable, headers objects have a guard property. This is not exposed to the Web, but it affects which mutation operations are allowed on the headers object.</p>
<p>Possible guard values are:</p>
<ul>
<li><code>none</code>: default.</li>
<li><code>request</code>: guard for a headers object obtained from a request ({{domxref("Request.headers")}}).</li>
<li><code>request-no-cors</code>: guard for a headers object obtained from a request created with {{domxref("Request.mode")}} <code>no-cors</code>.</li>
<li><code>response</code>: guard for a Headers obtained from a response ({{domxref("Response.headers")}}).</li>
<li><code>immutable</code>: Mostly used for ServiceWorkers; renders a headers object read-only.</li>
</ul>
<div class="note">
<p><strong>Note</strong>: You may not append or set a <code>request</code> guarded Headers’ <code>Content-Length</code> header. Similarly, inserting <code>Set-Cookie</code> into a response header is not allowed: ServiceWorkers are not allowed to set cookies via synthesized responses.</p>
</div>
<h2 id="Response_objects">Response objects</h2>
<p>As you have seen above, {{domxref("Response")}} instances are returned when <code>fetch()</code> promises are resolved.</p>
<p>The most common response properties you'll use are:</p>
<ul>
<li>{{domxref("Response.status")}} — An integer (default value 200) containing the response status code.</li>
<li>{{domxref("Response.statusText")}} — A string (default value "OK"), which corresponds to the HTTP status code message.</li>
<li>{{domxref("Response.ok")}} — seen in use above, this is a shorthand for checking that status is in the range 200-299 inclusive. This returns a {{domxref("Boolean")}}.</li>
</ul>
<p>They can also be created programmatically via JavaScript, but this is only really useful in {{domxref("ServiceWorker_API", "ServiceWorkers")}}, when you are providing a custom response to a received request using a {{domxref("FetchEvent.respondWith","respondWith()")}} method:</p>
<pre class="brush: js notranslate">var myBody = new Blob();
addEventListener('fetch', function(event) { // ServiceWorker intercepting a fetch
event.respondWith(
new Response(myBody, {
headers: { "Content-Type" : "text/plain" }
})
);
});</pre>
<p>The {{domxref("Response.Response","Response()")}} constructor takes two optional arguments — a body for the response, and an init object (similar to the one that {{domxref("Request.Request","Request()")}} accepts.)</p>
<ul>
</ul>
<div class="note">
<p><strong>Note</strong>: The static method {{domxref("Response.error","error()")}} simply returns an error response. Similarly, {{domxref("Response.redirect","redirect()")}} returns a response resulting in a redirect to a specified URL. These are also only relevant to Service Workers.</p>
</div>
<h2 id="Body">Body</h2>
<p>Both requests and responses may contain body data. A body is an instance of any of the following types:</p>
<ul>
<li>{{domxref("ArrayBuffer")}}</li>
<li>{{domxref("ArrayBufferView")}} (Uint8Array and friends)</li>
<li>{{domxref("Blob")}}/File</li>
<li>string</li>
<li>{{domxref("URLSearchParams")}}</li>
<li>{{domxref("FormData")}}</li>
</ul>
<p>The {{domxref("Body")}} mixin defines the following methods to extract a body (implemented by both {{domxref("Request")}} and {{domxref("Response")}}). These all return a promise that is eventually resolved with the actual content.</p>
<ul>
<li>{{domxref("Body.arrayBuffer","arrayBuffer()")}}</li>
<li>{{domxref("Body.blob","blob()")}}</li>
<li>{{domxref("Body.json","json()")}}</li>
<li>{{domxref("Body.text","text()")}}</li>
<li>{{domxref("Body.formData","formData()")}}</li>
</ul>
<p>This makes usage of non-textual data much easier than it was with XHR.</p>
<p>Request bodies can be set by passing body parameters:</p>
<pre class="brush: js notranslate">var form = new FormData(document.getElementById('login-form'));
fetch("/login", {
method: "POST",
body: form
});</pre>
<p>Both request and response (and by extension the <code>fetch()</code> function), will try to intelligently determine the content type. A request will also automatically set a <code>Content-Type</code> header if none is set in the dictionary.</p>
<h2 id="特性偵測">特性偵測</h2>
<p>想確認是否支持 Fetch API,可透過檢查 {{domxref("Headers")}}、{{domxref("Request")}}、{{domxref("Response")}} 或 {{domxref("GlobalFetch.fetch","fetch()")}} 是否存在 {{domxref("Window")}} 或 {{domxref("Worker")}} 域中。例如:</p>
<pre class="brush: js notranslate">if (self.fetch) {
// run my fetch request here
} else {
// do something with XMLHttpRequest?
}</pre>
<h2 id="Polyfill">Polyfill</h2>
<p>在不支援 Fetch 的瀏覽器, 可改用 <a href="https://github.com/github/fetch">Fetch Polyfill</a> 來重新支持缺少的 fetch 功能。</p>
<h2 id="技術指標">技術指標</h2>
<table class="standard-table">
<tbody>
<tr>
<th scope="col">技術名稱</th>
<th scope="col">狀態</th>
<th scope="col">說明</th>
</tr>
<tr>
<td>{{SpecName('Fetch')}}</td>
<td>{{Spec2('Fetch')}}</td>
<td>Initial definition</td>
</tr>
</tbody>
</table>
<h2 id="瀏覽器相容性">瀏覽器相容性</h2>
<div class="bc-data" id="bcd:api.WindowOrWorkerGlobalScope.fetch">
<p>{{Compat("api.WindowOrWorkerGlobalScope.fetch")}}</p>
</div>
<h2 id="參見">參見</h2>
<ul>
<li><a href="/en-US/docs/Web/API/ServiceWorker_API">ServiceWorker API</a></li>
<li><a href="/en-US/docs/Web/HTTP/Access_control_CORS">HTTP access control (CORS)</a></li>
<li><a href="/en-US/docs/Web/HTTP">HTTP</a></li>
<li><a href="https://github.com/github/fetch">Fetch polyfill</a></li>
<li><a href="https://github.com/mdn/fetch-examples/">Fetch examples on Github</a></li>
</ul>
|