没有脾气 發表於 2022-5-23 11:44:00

JavaScript – Fetch

<h2>前言</h2>
<p>上一篇&nbsp;JavaScript – XMLHttpRequest&nbsp;有提到&nbsp;XMLHttpRequest 正在被 Fetch 取代,这篇就继续介绍 Fetch 吧。</p>
<p>&nbsp;</p>
<h2>参考</h2>
<p>阮一峰 –&nbsp;Fetch API 教程</p>
<p>&nbsp;</p>
<h2>Simple Get Request &amp; Response</h2>
<div class="cnblogs_code">
<pre>const response = await fetch('https://192.168.1.152:44300/products'<span style="color: rgba(0, 0, 0, 1)">);
console.log(</span>'status'<span style="color: rgba(0, 0, 0, 1)">, response.status);
console.log(</span>'response', await response.text()); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 注:读取 response 数据是个异步过程哦,需要 await</span></pre>
</div>
<p>对比&nbsp;XMLHttpRequest 有几个点:</p>
<ol>
<li>Fetch 代码量比&nbsp;XMLHttpRequest 少了很多</li>
<li>Fetch 默认 request method 是 GET,所以不需要特别声明</li>
<li>Fetch 用 Promise 方式取代了&nbsp;XMLHttpRequest 的事件监听方式</li>
<li>Fetch 的 response 有 Stream 的概念,读取数据是异步的,需要 await Promise (Stream 还有其它特色,下面会教)。<br>另外,面对不同的 response type 需要使用不同的 response 方法做读取,比如读取 string 使用 response.text(),JSON 使用 response.json()。<br>这类似于 XMLHttpRequest 设置 request.responseType 属性。</li>




</ol>
<p>效果</p>
<p><img src="https://img2024.cnblogs.com/blog/641294/202402/641294-20240201233730388-900985129.png"></p>
<p>&nbsp;</p>
<h2>Request with Query Parameters</h2>
<p>Fetch 和 XMLHttpRequest 一样,都没有 built-in 对 Query Parameters 的处理。</p>
<p>我们需要借助&nbsp;URLSearchParams。</p>
<div class="cnblogs_code">
<pre>const searchParams = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> URLSearchParams({
key1: </span>'value1'<span style="color: rgba(0, 0, 0, 1)">,
});
const queryString </span>= '?' +<span style="color: rgba(0, 0, 0, 1)"> searchParams.toString();
const response </span>= await fetch('https://192.168.1.152:44300/products' + queryString);</pre>
</div>
<p>然后把 Query String 拼接到 request URL 就可以了。</p>
<p>&nbsp;</p>
<h2>Request with Header</h2>
<p>fetch 函数的第二参数可以设置 headers</p>
<div class="cnblogs_code">
<pre>const response = await fetch('https://192.168.1.152:44300/products'<span style="color: rgba(0, 0, 0, 1)">, {
headers: {
    Accept: </span>'application/json'<span style="color: rgba(0, 0, 0, 1)">,
},

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 用 Array Array 类型也是可以</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> headers: [</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">   ['Accept', 'application/json']</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ],</span>
});</pre>
</div>
<p>&nbsp;</p>
<h2>Request and Headers 对象</h2>
<p>我们也可以先创建 Request 和 Headers 对象,之后再传入 fetch 函数。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建 headers 对象</span>
const headers = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Headers({
Accept: </span>'application/json'<span style="color: rgba(0, 0, 0, 1)">,
});

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建 request 对象,并且传入 headers</span>
const request = <span style="color: rgba(0, 0, 255, 1)">new</span> Request('https://192.168.1.152:44300/products'<span style="color: rgba(0, 0, 0, 1)">, {
headers,
});

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 注意,虽然创建 Request 时有传入 Headers 对象,但是它不是相同的引用哦</span><span style="color: rgba(0, 128, 0, 1)">
//</span><span style="color: rgba(0, 128, 0, 1)"> 内部有 clone 的概念</span>
console.log(request.headers === headers); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> false</span>

<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 再添加一个 header</span>
request.headers.append('Custom-Header', 'value'<span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 发送请求</span>
const response = await fetch(request);</pre>
</div>
<p>fetch 函数的参数和 Request 对象 constructor 是一致的,不管上层接口是什么,底层实现都是&nbsp;Request +&nbsp;Headers + fetch 就对了。</p>
<h3>Clone Request</h3>
<p>Request 有一个 clone 方法,我们可以很简单的复制出一个请求</p>
<div class="cnblogs_code">
<pre>const request = <span style="color: rgba(0, 0, 255, 1)">new</span> Request('https://192.168.1.152:44300/products'<span style="color: rgba(0, 0, 0, 1)">, {
headers: {
    Accept: </span>'application/json'<span style="color: rgba(0, 0, 0, 1)">,
},
});
const request2 </span>=<span style="color: rgba(0, 0, 0, 1)"> request.clone();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 注意 clone 是深拷贝</span>
console.log(request.headers === request2.headers); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> false</span></pre>
</div>
<p>clone 是深拷贝哦。</p>
<p>&nbsp;</p>
<h2>Auto Parse JSON Response</h2>
<p>当 XMLHttpRequest 设置 request.responseType = ‘json',response 数据会自动被 JSON.parse 放入 request.response。</p>
<p>Fetch 没有 request.responseType 属性,取而代之的是 Response 对象上有各种类型方法。</p>
<div class="cnblogs_code">
<pre>const response = await fetch('https://192.168.1.152:44300/products'<span style="color: rgba(0, 0, 0, 1)">);
const products </span>= await response.json(); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> [{ id: 1, name: 'iPhone14' }, { id: 2, name: 'iPhone15' }]</span></pre>
</div>
<p>Fetch 的 response.text() 相等于 XMLHttpRequest 的 request.responseType = 'text'。</p>
<p>Fetch 的 response.json() 相等于 XMLHttpRequest 的 request.responseType = 'json'。</p>
<p>Fetch 的 response.blob() 相等于 XMLHttpRequest 的 request.responseType = 'blob'。</p>
<p>以此类推...</p>
<p>&nbsp;</p>
<h2>Read Response Body Multiple Times</h2>
<p>Fetch 的 Response 有 Stream 的概念,每一个 response 的 stream 只能被读取一次。</p>
<div class="cnblogs_code">
<pre>const response = await fetch('https://192.168.1.152:44300/products'<span style="color: rgba(0, 0, 0, 1)">);
console.log(await response.json()); // ok
console.log(await response.json()); // error</span></pre>
</div>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/blog/641294/202402/641294-20240202151403098-1814042795.png"></p>
<p>读取第二次就会报错。解决方法也非常简单,clone response 后才读取,就可以了。</p>
<div class="cnblogs_code">
<pre>const response = await fetch('https://192.168.1.152:44300/products'<span style="color: rgba(0, 0, 0, 1)">);
const response2 </span>=<span style="color: rgba(0, 0, 0, 1)"> response.clone();

console.log(await response.json());</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ok</span>
console.log(await response2.json()); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ok</span></pre>
</div>
<p>&nbsp;</p>
<h2>Response Header</h2>
<p>从 response.headers 对象中获取</p>
<div class="cnblogs_code">
<pre>const response = await fetch('https://192.168.1.152:44300/products'<span style="color: rgba(0, 0, 0, 1)">);
console.log(response.headers.get(</span>'Content-Type')); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 'application/json; charset=utf-8'</span>

<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> for loop all response headers</span>
<span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (const of response.headers.entries()) {
console.log(); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ['content-type', 'application/json; charset=utf-8']</span>
}</pre>
</div>
<p>如果遇到重复的 header 那么它会合并起来</p>
<p><img src="https://img2022.cnblogs.com/blog/641294/202205/641294-20220523164523514-1568396582.png"></p>
<p>key 是 'cutom',value 是 'a, b'</p>
<p>&nbsp;</p>
<h2 class="LC20lb MBeuO DKV0Md">Cross-Origin 跨域请求携带 Cookie</h2>
<p>跨域主要是服务端的职责,不熟悉的可以看这篇&nbsp;ASP.NET Core – CORS (Cross-Origin Resource Sharing)。</p>
<p>客户端和跨域有关的是 Cookie。</p>
<p>同跨 (same-origin) 请求,Fetch 会自动携带 Cookie。</p>
<p>跨域 (cross-origin) 请求,Fetch 默认不携带 Cookie,如果我们希望它携带 Cookie 可以设置 credentials。</p>
<div class="cnblogs_code">
<pre>const response = await fetch('https://192.168.1.152:44300/products'<span style="color: rgba(0, 0, 0, 1)">, {
credentials: </span>'include'<span style="color: rgba(0, 0, 0, 1)">,
});</span></pre>
</div>
<p>如果不希望 same-origin 携带 Cookie,可以这样设置&nbsp;credentials = 'omit'。</p>
<div class="cnblogs_code">
<pre>const response = await fetch('https://192.168.1.152:44300/products'<span style="color: rgba(0, 0, 0, 1)">, {
credentials: </span>'omit'<span style="color: rgba(0, 0, 0, 1)">,
});</span></pre>
</div>
<p>注:Fetch 这一点比 XMLHttpRequest 强,XMLHttpRequest 的&nbsp;withCredentials 无法控制 same-origin 是否携带 Cookie。</p>
<p>Fetch 默认&nbsp;credentials 是 'same-site',意思是只有 same-origin 请求会携带 Cookie,cross-origin 请求不携带 Cookie。</p>
<p>&nbsp;</p>
<h2>Request Error</h2>
<p>Request Error 指的是请求失败,通常是 offline 造成的。</p>
<p>status 400-5xx 这些可不算 Request Error 哦,因为这些已经是有 response 成功了,只是在 status 被区分是有问题的。</p>
<p>要处理 Request Error,可以使用 try catch。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
const response </span>= await fetch('https://192.168.1.152:44300/products'<span style="color: rgba(0, 0, 0, 1)">);
} </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (error) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(error <span style="color: rgba(0, 0, 255, 1)">instanceof</span><span style="color: rgba(0, 0, 0, 1)"> TypeError) {
    console.log(error.name);    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> TypeError</span>
    console.log(error.message); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Failed to fetch</span>
<span style="color: rgba(0, 0, 0, 1)">}
}</span></pre>
</div>
<p>其实它就是处理 Promise Rejection,使用 .then().catch 或 .then(() =&gt; {}, () =&gt; {}) 都可以。</p>
<p>&nbsp;</p>
<h2>Abort Request</h2>
<p>我们可以在任意时间终止一个 request。</p>
<p>创建 abort controller</p>
<div class="cnblogs_code">
<pre>const abortController = new AbortController();</pre>
</div>
<p>监听 abort 事件</p>
<div class="cnblogs_code">
<pre>abortController.signal.addEventListener('abort', () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
console.log(</span>'The request has been aborted, reason: ' +<span style="color: rgba(0, 0, 0, 1)"> abortController.signal.reason);
});</span></pre>
</div>
<p>signal.reason 是 abort 的原因,这点比&nbsp;XMLHttpRequest 更好,XMLHttpRequest 无法表述原因。</p>
<p>把 AbortController.signal 设置到 fetch request</p>
<div class="cnblogs_code">
<pre>await fetch('https://192.168.1.152:44300/products'<span style="color: rgba(0, 0, 0, 1)">, {
signal: abortController.signal,
});</span></pre>
</div>
<p>两秒钟后执行 abortController.abort 方法。</p>
<div class="cnblogs_code">
<pre>setTimeout(() =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
abortController.abort(</span>'reason of abort'); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> abort 时可以声明原因。</span>
}, 2000);</pre>
</div>
<p>fetch 内部会监听 abort 事件,当 abort 时会终止请求,并且通知服务端,请求被终止了。</p>
<p>ASP.NET Core 有一个 cancellationToken,可以随时查看是否客户端已经终止请求, 如果已经终止,那就不需要继续执行了。</p>
<p><img src="https://img2022.cnblogs.com/blog/641294/202205/641294-20220523184207835-1780223901.png"></p>
<p>Abort 会导致 fetch reject promise</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
const response </span>= await fetch('https://192.168.1.152:44300/products'<span style="color: rgba(0, 0, 0, 1)">, {
    signal: abortController.signal,
});
} </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (error: unknown) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (error <span style="color: rgba(0, 0, 255, 1)">instanceof</span><span style="color: rgba(0, 0, 0, 1)"> DOMException &amp;&amp; error.name === 'AbortError') {</span>
    console.log(error.message); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> The user aborted a request.</span>
    console.log(abortController.signal.reason); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> reason of abort</span>
<span style="color: rgba(0, 0, 0, 1)">}
}</span></pre>
</div>
<p>通过 catch + error instanceof DOMException,我们可以识别出 error 来自 abort。</p>
<p>&nbsp;</p>
<h2>Request Timeout</h2>
<p>XMLHttpRequest 可以通过&nbsp;request.timeout 设置超时限制,Fetch 没有类似的 built-in 设置。</p>
<p>我们只可以通过&nbsp;Abort Request 来实现超时限制。</p>
<div class="cnblogs_code">
<pre>const timeoutAbortController = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> AbortController();

const timeoutNumber </span>= setTimeout(() =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
timeoutAbortController.abort(</span>'Timeout, the request is taking more than five seconds.'<span style="color: rgba(0, 0, 0, 1)">);
}, </span>5000<span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
await fetch(</span>'https://192.168.1.152:44300/products'<span style="color: rgba(0, 0, 0, 1)">, {
    signal: timeoutAbortController.signal,
});
clearTimeout(timeoutNumber);
} </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (error) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (error </span><span style="color: rgba(0, 0, 255, 1)">instanceof</span> DOMException &amp;&amp; <span style="color: rgba(0, 0, 0, 1)">error.name </span>=== 'AbortError' &amp;&amp; <span style="color: rgba(0, 0, 0, 1)">(timeoutAbortController.signal.reason as string).startsWith(</span>'Timeout'<span style="color: rgba(0, 0, 0, 1)">)
) {
    console.log(timeoutAbortController.signal.reason);
}
}</span></pre>
</div>
<p>&nbsp;</p>
<h2>Download File</h2>
<p>除了请求 JSON 数据,偶尔我们也会需要下载文件。</p>
<h3>download txt file</h3>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/blog/641294/202402/641294-20240201210811455-556661098.png"></p>
<div class="cnblogs_code">
<pre>const response = await fetch('https://192.168.1.152:44300/data.txt'<span style="color: rgba(0, 0, 0, 1)">);
const memoryStream </span>=<span style="color: rgba(0, 0, 0, 1)"> await response.arrayBuffer();
const bytes </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Uint8Array(memoryStream);
const textDecoder </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> TextDecoder();
const text </span>=<span style="color: rgba(0, 0, 0, 1)"> textDecoder.decode(bytes);
console.log(text); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 'Hello World'</span></pre>
</div>
<p>关键是 response.arrayBuffer 方法,它会返回&nbsp;ArrayBuffer,再通过&nbsp;Uint8Array 和&nbsp;TextDecoder 从 ArrayBuffer 读取 data.txt 的内容。</p>
<h3>Download Video</h3>
<p>Video 通常 size 比较大,用 ArrayBuffer 怕内存会不够,所以用 Blob 会比较合适。</p>
<div class="cnblogs_code">
<pre>const response = await fetch('https://192.168.1.152:44300/video.mp4'<span style="color: rgba(0, 0, 0, 1)">);
const blob </span>=<span style="color: rgba(0, 0, 0, 1)"> await response.blob();
console.log(blob.size </span>/ 1024); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 124,645 kb</span>
console.log(blob.type);      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> video/mp4</span></pre>
</div>
<h4>download progress</h4>
<p>文件大下载慢,最好可以显示进度条</p>
<div class="cnblogs_code">
<pre>const response = await fetch('https://192.168.1.152:44300/video.mp4'<span style="color: rgba(0, 0, 0, 1)">);
const reader </span>= response.body!<span style="color: rgba(0, 0, 0, 1)">.getReader();
const totalBytes </span>= +response.headers.get('Content-Length')!<span style="color: rgba(0, 0, 0, 1)">;
let loadedBytes </span>= 0<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">while</span> (<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">) {
const { done, value } </span>=<span style="color: rgba(0, 0, 0, 1)"> await reader.read();
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (done) <span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
loadedBytes </span>+=<span style="color: rgba(0, 0, 0, 1)"> value.length;
const percentage </span>= ((loadedBytes / totalBytes) * 100).toFixed() + '%'<span style="color: rgba(0, 0, 0, 1)">;
console.log(percentage);
}</span></pre>
</div>
<p>XMLHttpRequest 有 progress 事件,Fetch 没有,所以要获取进度会比较麻烦。</p>
<p>首先需要通过 header&nbsp;Content-Length 获取 total。</p>
<p>接着利用 response body (类型是 ReadableStream) reader 分段式读取 response 内容。</p>
<p>然后拿每一次的内容长度做累加,最终计算出进度。</p>
<h4>partial data on downloading</h4>
<p>XMLHttpRequest 几乎无法做到分段式读取 response 内容 (即便做到也需要很多前提条件),但 Fetch 很容易,方案也很完整。</p>
<p>上面计算进度的例子中,我们就用到了分段式读取。</p>
<div class="cnblogs_code">
<pre>const response = await fetch('https://192.168.1.152:44300/video.mp4'<span style="color: rgba(0, 0, 0, 1)">);
const reader </span>= response.body!<span style="color: rgba(0, 0, 0, 1)">.getReader();
</span><span style="color: rgba(0, 0, 255, 1)">while</span> (<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">) {
const { done, value } </span>=<span style="color: rgba(0, 0, 0, 1)"> await reader.read();
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (done) <span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
console.log(value); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> value 的类型是 Uint8Array</span>
}</pre>
</div>
<p>&nbsp;</p>
<h2>POST Request</h2>
<p>POST 和 GET 大同小异</p>
<h3>POST JSON Data</h3>
<div class="cnblogs_code">
<pre>const product = { name: 'iPhone12'<span style="color: rgba(0, 0, 0, 1)"> };
const productJson </span>=<span style="color: rgba(0, 0, 0, 1)"> JSON.stringify(product);

const response </span>= await fetch('https://192.168.1.152:44300/products'<span style="color: rgba(0, 0, 0, 1)">, {
method: </span>'POST'<span style="color: rgba(0, 0, 0, 1)">,
headers: {
    Accept: </span>'application/json'<span style="color: rgba(0, 0, 0, 1)">,
    </span>'Content-Type': 'application/json'<span style="color: rgba(0, 0, 0, 1)">,
},
body: productJson,
});

console.log(response.status);       </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 201</span>
console.log(await response.json()); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> { id: 1, name: 'iPhone12' }</span></pre>
</div>
<p>JSON 格式需要添加 request header 'Content-Type',然后 body 放要发送的 JSON 数据就可以了。</p>
<h3>POST FormData or FormUrlEncoded&nbsp;(multipart/form-data or&nbsp;application/x-www-form-urlencoded)</h3>
<p>POST FormData or FormUrlEncoded 和 POST JSON data 大同小异</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> POST multipart/form-data</span>
const productFormData = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> FormData();
productFormData.set(</span>'name', 'iPhone12'<span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> POST application/x-www-form-urlencoded</span>
const productFormUrlEncoded = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> URLSearchParams({
name: </span>'iPhone12'<span style="color: rgba(0, 0, 0, 1)">,
});

const response </span>= await fetch('https://192.168.1.152:44300/products'<span style="color: rgba(0, 0, 0, 1)">, {
method: </span>'POST'<span style="color: rgba(0, 0, 0, 1)">,
headers: {
    Accept: </span>'application/json'<span style="color: rgba(0, 0, 0, 1)">,
},
body: productFormData,          </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> POST multipart/form-data</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> body: productFormUrlEncoded, // POST application/x-www-form-urlencoded</span>
});</pre>
</div>
<p>只是把 send 的数据从 JSON 换成 FormData or&nbsp;URLSearchParams 就可以了。</p>
<p>另外,FormData or FormUrlEncoded 不需要设置 request header 'Content-Type',游览器会依据发送的数据类型自动添加,JSON 之所以需要是因为游览器把 JSON 视为 text/plain。</p>
<h3>POST Binary (Blob)</h3>
<p>FormData 支持 Blob 类型的 value,所以我们可以使用 FormData 上传二进制文件。</p>
<div class="cnblogs_code">
<pre>const productFormData = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> FormData();
productFormData.set(</span>'name', 'iPhone12'<span style="color: rgba(0, 0, 0, 1)">);
const productDocument </span>= 'Product Detail'<span style="color: rgba(0, 0, 0, 1)">;
const textEncoder </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> TextEncoder();
const bytes </span>=<span style="color: rgba(0, 0, 0, 1)"> textEncoder.encode(productDocument);
const blob </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Blob(, {
type: </span>'text/plain'<span style="color: rgba(0, 0, 0, 1)">,
});
productFormData.set(</span>'document'<span style="color: rgba(0, 0, 0, 1)">, blob);

const response </span>= await fetch('https://192.168.1.152:44300/products'<span style="color: rgba(0, 0, 0, 1)">, {
method: </span>'POST'<span style="color: rgba(0, 0, 0, 1)">,
headers: {
    Accept: </span>'application/json'<span style="color: rgba(0, 0, 0, 1)">,
},
body: productFormData,
});</span></pre>
</div>
<p>或者直接发送 Blob 也是可以的。</p>
<div class="cnblogs_code">
<pre>const productDocument = 'Product Detail'<span style="color: rgba(0, 0, 0, 1)">;
const textEncoder </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> TextEncoder();
const bytes </span>=<span style="color: rgba(0, 0, 0, 1)"> textEncoder.encode(productDocument);
const blob </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Blob(, {
type: </span>'text/plain', <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 如果二进制没有明确类型,type 就放 application/octet-stream</span>
<span style="color: rgba(0, 0, 0, 1)">});

const response </span>= await fetch('https://192.168.1.152:44300/products'<span style="color: rgba(0, 0, 0, 1)">, {
method: </span>'POST'<span style="color: rgba(0, 0, 0, 1)">,
headers: {
    Accept: </span>'application/json'<span style="color: rgba(0, 0, 0, 1)">,
},
body: blob,
});</span></pre>
</div>
<h4>upload progress</h4>
<p>非常遗憾,Fetch 完全不支持 upload progress。这也是为什么&nbsp;XMLHttpRequest 还没有完全被取代的原因。</p>
<p>Chromium 105 推出了&nbsp;Streaming requests with the fetch API&nbsp;可以解决这个问题,但是其它游览器支持度不高。</p>
<p>&nbsp;&nbsp;</p>
<h2>Request ReadyState</h2>
<p>XMLHttpRequest 有 request.readyState 和&nbsp;readystatechange 事件可以获知 Request 的不同阶段。</p>
<p>Fetch 没有这些,我们需要自己写</p>
<div class="cnblogs_code">
<pre>const response = await fetch('https://192.168.1.152:44300/video.mp4'<span style="color: rgba(0, 0, 0, 1)">);
console.log(</span>'readystatechange', '2. headers received'<span style="color: rgba(0, 0, 0, 1)">);
const total </span>= +response.headers.get('Content-Length')!<span style="color: rgba(0, 0, 0, 1)">;
const reader </span>= response.body!<span style="color: rgba(0, 0, 0, 1)">.getReader();
console.log(</span>'readystatechange', '3. loading'<span style="color: rgba(0, 0, 0, 1)">);
let loaded </span>= 0<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">while</span> (<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">) {
const { done, value } </span>=<span style="color: rgba(0, 0, 0, 1)"> await reader.read();
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (done) <span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
loaded </span>+= value!<span style="color: rgba(0, 0, 0, 1)">.length;
console.log(</span>'progress'<span style="color: rgba(0, 0, 0, 1)">, );
}
console.log(</span>'readystatechange', '4. done');</pre>
</div>
<p>类似这样。</p>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/keatkeat/p/16300650.html
頁: [1]
查看完整版本: JavaScript – Fetch