第一阶段:发送响应头response header,响应头解析

一、当服务器发送了响应头(response header),fetch返回的promise就使用内建的Response class对象来对响应头进行解析。
二、在这个阶段,我们可以检查响应头,来检查HTTP状态以确定请求是否成功,目前还没有响应体(response body)。
三、如果fetch无法建立一个HTTP请求,例如网络问题,亦或是请求的网址不存在,那么promise就会reject。异常的HTTP状态,例如404或500,不会导致出现error。

响应的属性

一、响应的属性:
1、response.status —— response 的 HTTP 状态码,
2、response.ok —— HTTP 状态码为 200-299,则为 true。
3、response.headers —— 类似于 Map 的带有 HTTP header 的对象。
二、我们可以在response的属性中看到HTTP状态
1、status:HTTP状态码,如200
2、ok:布尔值,如果HTTP状态码为200-299,则为true。
【示例1】

  1. let response = await fetch(url);
  2. if (response.ok) { // 如果HTTP状态码为200-299
  3. // 获取response body(此方法会在下面解释)
  4. let json = await response.json();
  5. } else {
  6. alert('HTTP-Error:' + response.status);
  7. }

Response header

一、Response header位于response.headers中的一个类似于Map的header对象。
二、它不是真正的Map,但是它具有类似的方法,我们可以按名称(name)获取各个header,或迭代它们

  1. let response = await fecth('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits');
  2. // 获取一个header
  3. alert(response.headers.get('Content-Type')); // application/json;charset=utf-8
  4. // 迭代所有header
  5. for (let [key, value] of response.headers) {
  6. alert(`${key} = ${value}`);
  7. }

第二阶段:获取response body

一、为了获取response body,我们需要使用一个其他的方法调用。
二、Response提供了多种基于promise的方法,来以不同的格式访问body,如response.text()等。

获取response body的方法

一、1、response.text():读取resposne,并以文本形式返回response。
2、response.json():将response解析为JSON
3、resposne.formData():以FromData对象的形式返回response
4、response.blob():以Blob(具有类型的二进制数据)形式返回response。
5、response.arrayBuffer():以ArrayBuffer(低级别的二进制数据)形式返回response
6、response.body:是ReadableStream对象,它允许你逐块读取body
二、只能选择一种读取body的方法
【示例1】如果我们已经使用了response.text()方法来获取response,那么如果再用response.json(),则不会生效,因为body内容已经被处理过了。

  1. let text = await response.text(); // respose body被处理了
  2. let parsed = await response.json(); // 失败(已经被处理过了)

response.body

一、response.body是ReadableStream对象,它允许你逐块读取body.
【示例1】从GitHub读取最新commits的JSON对象

  1. let url = 'https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits';
  2. let response = await fetch(url);
  3. let commits = await response.json(); // 读取response body,并将其解析为JSON
  4. alert(commits[0].author.login);

1、如果github地址是:https://github.com/ljianshu/Blog,可以通过github的api:https://api.github.com/repos/ljianshu/Blog/commits获取到commits信息。
【示例2】也可以使用纯promise语法,不使用await:

  1. fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits')
  2. .then(response => response.json())
  3. .then(commits => alert(commits[0].author.login));

【示例3】要获取响应文本,可以使用await response.text()代替.json()

  1. let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits');
  2. let text = await response.text(); // 将 response body 读取为文本
  3. alert(text.slice(0, 80) + '...');

【示例4】读取为二进制格式,fetch并显示一张“fetch”规范中的图片

  1. let response = await fetch('/article/fetch/logo-fetch.svg');
  2. let blob = await response.blob(); // 下载为Blob对象
  3. // 为其创建一个<img>
  4. let img = document.createElement('img');
  5. img.style = 'position:fixed;top:10px;left:10px;width:100px;';
  6. document.body.append(img);
  7. // 显示它
  8. img.src = URL.createObjectURL(blob);
  9. setTimeout(() => { // 3秒后将其隐藏
  10. img.remove();
  11. URL.revokeObjectURL(img.src);
  12. }, 3000);

Fetch:下载进度

一、fetch方法允许去跟踪下载进度。
二、20210407:目前为止,fetch方法无法跟踪上传进度。可以使用XMLHttpRequest。
三、要跟踪下载进度,我们可以使用response.body属性。它是ReadableStream:1个特殊的对象,它可以逐块(chunk)提供body。
三、与response.text(), response.json()和其他方法不同,response.body给予了对进度读取的完全控制,我们看随时计算下载了多少
四、从response.body读取response

  1. // 代替response.json()以及其他方法
  2. const reader = response.body.getReader();
  3. // 在body下载时,一直为无限循环
  4. while(true) {
  5. // 当最后一块下载完成时,done值为true
  6. const { done, value } = await reader.read();
  7. if (done) {
  8. break;
  9. }
  10. console.log(`Received ${value.length} bytes`);
  11. }

1、await reader.read()调用的结果时一个具有两个属性的对象
(1)done:当读取完成时为true,否则为false
(2)value:字节的类型化数组:Unit8Array。
2、我们在循环中接收响应快(response chunk),直到加载完成,也就是说:直到done为true
3、要将进度打印出来,我们只需要将每个接收到的片段value的长度(length)加到conter即可
五、获取响应,并在控制台记录进度的完整工作示例

  1. // Step 1:启动 fetch,并获得一个 reader
  2. let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits?per_page=100');
  3. const reader = response.body.getReader();
  4. // Step 2:获得总长度(length)
  5. const contentLength = +response.headers.get('Content-Length');
  6. // Step 3:读取数据
  7. let receivedLength = 0; // 当前接收到了这么多子节
  8. let chunks = []; // 接收到的二进制块的数组(包括body)
  9. while(true) {
  10. const { done, value } = await reader.read();
  11. if (done) {
  12. break;
  13. }
  14. chunks.push(value);
  15. receivedLength += value.length;
  16. console.log(`Received ${receivedLength} of ${contentLength}`)
  17. }
  18. // Step4:将块连接到单个Unit8Array
  19. let chunksAll = new Unit8Array(receivedLength);
  20. let position = 0;
  21. for (let chunk of chunks) {
  22. chunkAll.set(chunk, position);
  23. position += chunk.length;
  24. }
  25. // Step5:解析成字符串
  26. let result = new TextDecoder('utf-8').decode(chunksAll);
  27. // 完成
  28. let commits = JSON.parse(result);
  29. alert(commits[0].author.login);

1、我们像往常一样执行 fetch,但不是调用 response.json(),而是获得了一个流读取器(stream reader)response.body.getReader()。
(1)我们不能同时使用这两种方法来读取相同的响应。要么使用流读取器,要么使用 reponse 方法来获取结果。
2、在读取数据之前,我们可以从 Content-Length header 中得到完整的响应长度。
(1)跨源请求中可能不存在这个 header,并且从技术上讲,服务器可以不设置它。但是通常情况下它都会在那里。
3、调用 await reader.read(),直到它完成。
(1)我们将响应块收集到数组 chunks 中。
① 在使用完(consumed)响应后,我们将无法使用 response.json() 或者其他方式(你可以试试,将会出现 error)去“重新读取”它。
4、最后,我们有了一个chunks—— 一个Uint8Array字节块数组。我们需要将这些块合并成一个结果。
(1)没有单个方法可以将它们串联起来,所以这里需要一些代码来实现:
① 我们创建 chunksAll = new Uint8Array(receivedLength) —— 一个具有所有数据块合并后的长度的同类型数组。
② 然后使用 .set(chunk, position) 方法,从数组中一个个地复制这些 chunk。
5、我们的结果现在储存在 chunksAll 中。但它是一个字节数组,不是字符串。
(1)要创建一个字符串,我们需要解析这些字节。可以使用内建的 TextDecoder 对象完成。然后,我们可以 JSON.parse 它,如果有必要的话。
(2)如果我们需要的是二进制内容而不是字符串呢?
① 用下面这行代码替换掉第 4 和第 5 步,这行代码从所有块创建一个 Blob:

  1. let blob = new Blob(chunks);

6、最后,我们得到了结果(以字符串或 blob 的形式表示,什么方便就用什么),并在过程中对进度进行了跟踪。