[关闭]
@jsongao98 2021-04-24T12:13:06.000000Z 字数 9087 阅读 54

网络请求(Ajax)

JavaScript


fetch

let response = await fetch(url , [options]);//解析响应头
let result = await response.json(); // 将 body 读取为 json

或者用promise链的方式
fetch(url, options)
.then(response => response.json())
.then(result => /* process result */)

  • 第一阶段,当服务器发送了响应头(response header),fetch 返回的 promise 就使用内建的 Response class 对象来对响应头进行解析。如果 fetch 无法建立一个 HTTP 请求,例如网络问题,亦或是请求的网址不存在,那么 promise 就会 reject。异常的 HTTP 状态,例如 404 或 500,不会导致出现 error。

    返回的response的属性:

  • response.status ——— http状态码
  • response.ok —— 布尔值,如果 HTTP 状态码为 200-299,则为 true。
  • response.headers —— 类似于 Map 的带有 HTTP header 的对象。

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

    1. let response = await fetch('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

    respronse基于promise的方法,以不同格式访问body,而且只能使用一种:

  • response.text() —— 读取 response,并以文本形式返回 response,
  • response.json() —— 将 response 解析为 JSON,
  • response.formData() —— 以 FormData 对象(在 下一章 有解释)的形式返回 response,
  • response.blob() —— 以 Blob(具有类型的二进制数据)形式返回 response,
  • response.arrayBuffer() —— 以 ArrayBuffer(低级别的二进制数据)形式返回 response,


  • Request header/method/body

    • headers :
      • Accept
      • Accept-Language
      • Accept-Charset
      • Accept-Encoding
      • API-Key
      • Content-Language
      • Content-Type
      • Connection
      • Host
      • Origin (protocol/domain/port)
      • Referer (包含当前页面url:scheme://host.domain:port/path/filename)
        • 我们需要 Origin,是因为有时会没有 Referer。例如,当我们从 HTTPS(从高安全性访问低安全性)fetch HTTP 页面时,便没有 Referer。 内容安全策略 可能会禁止发送 Referer。 正如我们将看到的,fetch 也具有阻止发送 Referer 的选项,甚至允许修改它(在同一网站内)。 根据规范,Referer 是一个可选的 HTTP-header。 正是因为 Referer 不可靠,才发明了 Origin。浏览器保证跨源请求的正确 Origin。
      • credentials: "include"
        • 对应的response header:Access-Control-Allow-Credentials :true


    • method : —— HTTP 方法,例如 POST,

    • body : —— request body,其中之一:

      • 字符串(例如 JSON 编码的),
      • FormData 对象,以 form/multipart 形式发送数据,带有 Content-Type: multipart/form-data
        • new Fromdata(form) : .append()/.get()/.set()/.has()/.delete()
        • 发送带文件的表单:
          input type='file' name='picture' accept='image/*' 是被编码允许的
        • 发送带blob的表单:
          let imageBlob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png'));
          let formData = new FormData();
          formData.append("image", imageBlob, "image.png");
          就像表单中有 input type="file" name="image"一样,用户从他们的文件系统中使用数据 imageBlob(第二个参数,blob类型)提交了一个名为 image.png(第三个参数)的文件。

      • Blob/BufferSource 发送二进制数据,


    下载进度

    fetch 方法无法跟踪 上传 进度,允许去跟踪 下载 进度。

    1. // 代替 response.json() 以及其他方法
    2. const reader = response.body.getReader();
    3. // 在 body 下载时,一直为无限循环,循环下载response chunk
    4. while(true) {
    5. // 当最后一块下载完成时,done 值为 true
    6. // value 是块字节的 Uint8Array
    7. const {done, value} = await reader.read(); //await reader.read() 调用的结果是一个具有两个属性的对象:
    8. //done —— 当读取完成时为 true,否则为 false。
    9. //value —— 字节的类型化数组:Uint8Array。
    10. if (done) {
    11. break;
    12. }
    13. console.log(`Received ${value.length} bytes`)
    14. }

    中止(Abort)

    一个特殊的内建对象:AbortController。它不仅可以中止 fetch,还可以中止其他异步任务。
    abort()方法
    signal属性:当abort()被调用时,signal.aborted = true

  • 与fetch一起使用

    1. let controller = new AbortController();
    2. let response = await fetch('/article/fetch-abort/demo/hang', {
    3. signal: controller.signal//fetch同样监听controller的signal属性的变化
    4. });
    1. let urls = [...]; // 要并行 fetch 的 url 列表
    2. let controller = new AbortController();
    3. // 一个 fetch promise 的数组
    4. let fetchJobs = urls.map(url => fetch(url, {
    5. signal: controller.signal
    6. }));
    7. let results = await Promise.all(fetchJobs);
    8. // 如果 controller.abort() 被从其他地方调用,
    9. // 它将中止所有 fetch

  • 与其他异步任务一起使用

    1. let urls = [...];
    2. let controller = new AbortController();
    3. let ourJob = new Promise((resolve, reject) => { // 我们的任务 ...
    4. controller.signal.addEventListener('abort', reject);//************************
    5. });
    6. let fetchJobs = urls.map(url => fetch(url, { // fetches
    7. signal: controller.signal
    8. }));
    9. // 等待完成我们的任务和所有 fetch
    10. let results = await Promise.all([...fetchJobs, ourJob]);
    11. // 如果 controller.abort() 被从其他地方调用,
    12. // 它将中止所有 fetch 和 ourJob

  • 跨域请求的解决方案:因为浏览器同源策略限制

    一个源(域domain/端口port/协议protocal三者)无法获取另一个源(origin)的内容。因此,即使我们有一个子域,或者仅仅是另一个端口,这都是不同的源,彼此无法相互访问。

    ① jsonp

    ② 跨源请求(CORS)

    如果一个请求是跨源的,浏览器始终会向其添加 Origin header。

  • Resposne header:
  • 对于跨源请求,默认情况下,JavaScript 只能访问“简单” 的response header:

      • Cache-Control
      • Content-Language
      • Content-Type
      • Expires
      • Last-Modified
      • Pragma
    访问任何其他 response header 都将导致 error。
    要授予 JavaScript 对任何其他 response header 的访问权限,服务器必须发送Access-Control-Expose-Headers header。它包含一个以逗号分隔的应该被设置为可访问的非简单 header 名称列表。比如:
    Access-Control-Expose-Headers: Content-Length , API-Key , ...

    • 简单请求:
      • 方法:GET / POST / HEAD
      • 简单请求的header仅能设置:(这三种请求如果自定义请求头的话也会发送option预检请求)
        • Accept
        • Accept-Language
        • Content-Language
        • Content-Type 的值为 application/x-www-form-urlencoded,multipart/form-data 或 text/plain。
      • 简单请求和其他请求的本质区别在于,自古以来使用 formscript 标签进行简单请求就是可行的(标签需要具有 crossorigin 特性),而长期以来浏览器都不能进行非简单请求。
        所以,实际区别在于,简单请求会使用 Origin header 并立即发送,而对于其他请求,浏览器会发出初步的“预检”请求,以请求许可。

                 此处输入图片的描述

    • 其他任意请求
    • https://juejin.cn/post/6844903821634699277

      任何“非标准”请求 —— 浏览器不会立即发出在过去无法完成的这类请求。即在它发送这类请求前,会先发送“预检(preflight)”请求来请求许可。

      • Step1 预检请求(preflight request)
        1. OPTIONS /service.json
        2. Host: site.com
        3. Origin: https://javascript.info
        4. Access-Control-Request-Method: PATCH
        5. Access-Control-Request-Headers: Content-Type,API-Key
      • Step2 预检响应(preflight response)
        1. 200 OK
        2. Access-Control-Allow-Origin: https://javascript.info
        3. Access-Control-Allow-Methods: PUT,PATCH,DELETE
        4. Access-Control-Allow-Headers: API-Key,Content-Type,If-Modified-Since,Cache-Control
        5. Access-Control-Max-Age: 86400
      • Step3 实际请求(actual request)
        1. PATCH /service.json
        2. Host: site.com
        3. Content-Type: application/json
        4. API-Key: secret
        5. Origin: https://javascript.info
      • Step4 实际响应(actual response)
        1. Access-Control-Allow-Origin: https://javascript.info

                 此处输入图片的描述

      ③ 代理服务器(不需要前后端协同)


    URL对象

    此处输入图片的描述

    • href 是完整的 URL,与 url.toString() 相同
    • protocol 以冒号字符 : 结尾
    • search —— 以问号 ? 开头的一串参数
    • hash 以哈希字符 # 开头
    • 如果存在 HTTP 身份验证,则这里可能还会有 user 和 password 属性:http://login:password@site.com(图片上没有,很少被用到)。

    XHR(XMLHttpRequest)

  • 创建:
    1. let xhr = new XMLHttpRequest();

  • 配置请求:
    1. xhr.open(method,URL,[async,user,password])

    同步请求: open 方法中将第三个参数 async 设置为 false,那么请求就会以同步的方式进行。
    换句话说,JavaScript 执行在 send() 处暂停,并在收到响应后恢复执行。这有点儿像 alert 或 prompt 命令。

  • 发送请求:
    1. xhr.send([body])

  • 监听xhr对象事件以获得响应:
        • 按生命周期:
      • loadstart —— 请求开始。
      • progress —— 仅下载阶段触发,一个响应数据包到达,此时整个 response body 都在 response 中。
      • abort —— 调用 xhr.abort() 取消了请求, 触发abort事件,且 xhr.status 变为0 。
      • error —— 发生连接错误,例如,域错误。不会发生诸如 404 这类的 HTTP 错误。
      • load —— 请求成功完成。
      • timeout —— 由于请求超时而取消了该请求(仅发生在设置了 timeout 的情况下)。
      • loadend —— 在 load,error,timeout 或 abort 之后触发。error,abort,timeout 和 load 事件是互斥的。其中只有一种可能发生。

        • 服务器有了响应后,从xhr对象属性中获取响应状态:
      • status —— HTTP 状态码(一个数字):200,404,403 等,如果出现非 HTTP 错误,则为 0。
      • statusText —— HTTP 状态消息(一个字符串):状态码为 200 对应于 OK,404 对应于 Not Found,403 对应于 Forbidden。
      • response —— 服务器 response body。
      • timeout —— 如果在给定时间内请求没有成功执行,请求就会被取消,并且触发 timeout 事件。

        • 用 xhr.responseType来设置响应类型:
      • ""(默认)—— 响应格式为字符串,
      • "text" —— 响应格式为字符串,
      • "arraybuffer" —— 响应格式为 ArrayBuffer(对于二进制数据,请参见 ArrayBuffer,二进制数组),
      • "blob" —— 响应格式为 Blob(对于二进制数据,请参见 Blob),
      • "document" —— 响应格式为 XML document(可以使用 XPath 和其他 XML 方法),
      • "json" —— 响应格式为 JSON(自动解析)。

        • xhr.readyState

        1. UNSENT = 0; // 初始状态
        2. OPENED = 1; // open 被调用
        3. HEADERS_RECEIVED = 2; // 接收到 response header
        4. LOADING = 3; // 响应正在被加载(接收到一个数据包)
        5. DONE = 4; // 请求完成
      • XMLHttpRequest 对象以 0 → 1 → 2 → 3 → … → 3 → 4的顺序在它们之间转变。每当通过网络接收到一个数据包,就会重复一次状态 3。


  • HTTP-header
  • XMLHttpRequest 允许发送自定义 header,并且可以从响应中读取 header。

      • setRequestHeader(name,value)
        1. xhr.setRequestHeader('Content-Type', 'application/json');
        能设置的请求头有限制且一旦设置不能被移除。

      • getResponseHeader(name)
        获取具有给定 name 的 header(Set-Cookie 和 Set-Cookie2 除外)

      • getAllResponseHeaders()
        返回除 Set-Cookie 和 Set-Cookie2 外的所有 response header。


  • 上传进度:xhr.upload对象
  • 它没有方法,它专门用于跟踪上传事件。它会生成事件,类似于 xhr,但是 xhr.upload 仅在上传时触发它们:

      • loadstart —— 上传开始。
      • progress —— 上传期间定期触发。
      • abort —— 上传中止。
      • error —— 非 HTTP 错误。
      • load —— 上传成功完成。
      • timeout —— 上传超时(如果设置了 timeout 属性)。
      • loadend —— 上传完成,无论成功还是 error。


  • 跨源请求
  • XMLHttpRequest 可以使用和 fetch 相同的 CORS 策略进行跨源请求。
    就像 fetch 一样,默认情况下不会将 cookie 和 HTTP 授权发送到其他域。要启用它们,可以将 xhr.withCredentials 设置为 true


    短轮询,长轮询,长连接, WebSocket

    • 短轮询
      • 基本思路就是浏览器每隔一段时间向浏览器发送http请求,服务器端在收到请求后,不论是否有数据更新,都直接进行响应。这种方式实现的即时通信,本质上还是浏览器发送请求,服务器接受请求的一个过程,通过让客户端不断的进行请求,使得客户端能够模拟实时地收到服务器端的数据的变化。
      • 不足:
      • 消息传递的延迟最多为 10 秒(两个请求之间)
      • 即使没有消息,服务器也会每隔 10 秒被请求轰炸一次,即使用户切换到其他地方或者处于休眠状态,也是如此。就性能而言,这是一个很大的负担。
      • 因此短轮询不适用于那些同时在线用户数量比较大,并且很注重性能的Web应用。

    • 长轮询
      • 当服务器收到客户端发来的请求后,服务器端不会直接进行响应,而是先将这个请求挂起,然后判断服务器端数据是否有更新。如果有更新,则进行响应,如果一直没有数据,则到达一定的时间限制(服务器端设置)才返回。 客户端JavaScript响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接。如果连接丢失,可能是因为网络错误,浏览器会立即发送一个新请求。
      • 优点:长轮询和短轮询比起来,明显减少了很多不必要的http请求次数,相比之下节约了资源。
      • 缺点:长轮询的缺点在于,连接挂起也会导致服务器端资源的浪费。
      • 使用场景: 在消息很少的情况下,长轮询很有效
        消息比较频繁,那么下面描绘的请求-接收(requesting-receiving)消息的图表就会变成锯状状(saw-like)。 每个消息都是一个单独的请求,并带有 header,身份验证开销(authentication overhead)等。
        因此,在这种情况下,首选另一种方法,例如:Websocket 或 Server Sent Events。

                此处输入图片的描述

    • WebSocket
      1. let socket = new WebSocket("ws://javascript.info");//我们更应该使用 wss:// 加密协议
      2. // wss:// 是基于 TLS 的 WebSocket,类似于 HTTPS 是基于 TLS 的 HTTP,传输安全层在发送方对数据进行了加密,在接收方进行解密。
      WebSocket是Html5定义的一个新协议,与传统的http协议不同,该协议可以实现服务器与客户端之间全双工通信。
      简单来说,首先需要在客户端和服务器端建立起一个连接,这部分需要http。连接一旦建立,客户端和服务器端就处于平等的地位,可以相互发送数据,不存在请求和响应的区别。

      • WebSocket 没有跨源限制。
      • 浏览器对 WebSocket 支持很好。
      • 可以发送/接收字符串和二进制数据。

        WebSocket事件
      • open —— 连接已建立
      • message —— 接收到数据
      • error —— WebSocket 错误
      • close —— 连接已关闭

        WebSocket 方法
      • socket.send(data)
      • socket.close([code], [reason])

      WebSocket 自身并不包含重新连接(reconnection),身份验证(authentication)和很多其他高级机制。因此,有针对于此的客户端/服务端的库,并且也可以手动实现这些功能。

    • 长连接(Server Sent Events)
    • SSE是HTML5新增的功能,全称为Server-Sent Events。它可以允许服务推送数据到客户端。SSE在本质上就与之前的长轮询、短轮询不同,虽然都是基于http协议的,但是轮询需要客户端先发送请求。而SSE最大的特点就是不需要客户端发送请求,可以实现只要服务器端数据有更新,就可以马上发送到客户端。

      SSE的优势很明显,它不需要建立或保持大量的客户端发往服务器端的请求,节约了很多资源,提升应用性能。

    杂项

      fetch目前遇到的问题:
    • fetch只对网络请求报错,对400,500都当做成功的请求,需要封装去处理
    • fetch默认不会带cookie,需要添加配置项。
    • fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费。
    • fetch没有办法原生监测请求的进度,而XHR可以。

      ajax请求和普通http请求的相同不同:

  • 首先最早是没有Ajax请求的,只有普通的HTTP请求,这个时候发送一次HTTP请求,Server端就会计算后将数据放在一个HTML网页上返回来,客户端需要刷新网页,也就是每次请求都刷新网页。Ajax请求实现了返回xml或者json数据而不是html,然后支持在html不变情况下动态更新页面内容而无需刷新。

  • 不管传统的HTTP请求还是Ajax请求,都有同步和异步两种选项。
    配置为同步之后会阻塞浏览器页面的线程(也可能是进程),返回结果前客户端不再响应用户请求。配置为异步之后不会阻塞浏览器线程,继续进行浏览器渲染和响应用户操作,直到response返回后回调函数处理结果。

  • 发现ajax的请求,多了一个“X-Requested-With”属性。

  • 添加新批注
    在作者公开此批注前,只有你和作者可见。
    回复批注