最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • JavaScript中的网络请求

    正文概述 掘金(TDWX)   2020-12-13   308

    Fetch 基本使用

    fetch 返回一个 promise ,常用方式:

    let response = await fetch(url, options); // 解析 response header
    let result = await response.json(); // 将 body 读取为 json
    
    • options :
      • method :HTTP 方法,如 get、post……
      • headers :请求头,但是有一些 header 是不被允许的,详见规范 
      • body :request body,要以 string , FormData , BufferSource , Blob  或 UrlSearchParams  对象的形式发送的数据

    接收到的 response 属性如下:

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

    获取 response body :

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

    FormData

    发送表单

    FormData 对象可以发送表单对象内容,比如:

    <form id="formElem">
      <input type="text" name="name" value="John">
      <input type="text" name="surname" value="Smith">
      <input type="submit">
    </form>
    
    <script>
      formElem.onsubmit = async (e) => {
        e.preventDefault();
    
        let response = await fetch('/article/formdata/post/user', {
          method: 'POST',
          body: new FormData(formElem)
        });
    
        let result = await response.json();
    
        alert(result.message);
      };
    </script>
    

    这样可以把整个表单对象发送出去。

    我们也可以通过以下方法去修改添加表单对象到 FormData  :

    • formData.append(name, value)  —— 添加具有给定 name 和 value 的表单字段,
    • formData.append(name, blob, fileName)  —— 添加一个字段,就像它是<input type='file'> ,第三个参数 fileName 设置文件名(而不是表单字段名),因为它是用户文件系统中文件的名称
    • formData.set(name,value) —— set 方法会清楚当前 formData 中所有此 name 的字段,然后插入新的 value
    • formData.set(name,blob,value) —— 原理同上
    • formData.delete(name)  —— 移除带有给定 name 的字段,
    • formData.get(name)  —— 获取带有给定 name 的字段值,
    • formData.has(name)  —— 如果存在带有给定 name 的字段,则返回 true,否则返回 false。

    FormData 对象是可以循环的 : for(let[key,value] of formData){}

    发送文件

     <input type="file"> 允许我们发送一个文件:

    <input type="file" id="pic">
      
    <script>
      let formData = new FormData()
      pic.onchange = function(e){
    		formData.append("fileIn",this.value)
    	}
      let response = await fetch('/xxx', {
        method: 'POST',
        body: formData
      });
    
      let result = await response.json();
      
      
    </script>
    

    下载进度

    可以通过 response.body ,它是 ReadableStream —— 一个特殊的对象,它可以逐块(chunk)提供 body。在 Streams API 规范中有对 ReadableStream 的详细描述。 然后可以通过他的 getReader() 来获取一个流读取器(stream reader):

    // Step 1:启动 fetch,并获得一个 reader
    let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits?per_page=100');
    
    const reader = response.body.getReader();
    
    // Step 2:获得总长度(length)
    const contentLength = +response.headers.get('Content-Length');
    
    // Step 3:读取数据
    let receivedLength = 0; // 当前接收到了这么多字节
    let chunks = []; // 接收到的二进制块的数组(包括 body)
    while(true) {
      // read() 会返回done —— 读取完成时是 true,否则 false
      //还有 value —— 字节的类型化数组:Uint8Array。
      const {done, value} = await reader.read();
    
      if (done) {
        break;
      }
    	// 每拿到一块 就放入二进制数组
      chunks.push(value);
      receivedLength += value.length;
    
      console.log(`Received ${receivedLength} of ${contentLength}`)
    }
    // 接收已完成,以下是处理过程。
    // Step 4:将块连接到单个 Uint8Array
    let chunksAll = new Uint8Array(receivedLength); // (4.1)
    let position = 0;
    for(let chunk of chunks) {
      chunksAll.set(chunk, position); // (4.2)
      position += chunk.length;
    }
    
    // Step 5:解码成字符串
    let result = new TextDecoder("utf-8").decode(chunksAll);
    
    // 我们完成啦!
    let commits = JSON.parse(result);
    

    Fetch 中止

    我们有一个 AbortController 对象,他的用法很简单:

    let aContro = new AbortController()
    // 属性 signal,我们可以在这个属性上设置事件监听器。
    let singal = aContro.signal
    // 方法 abort()
    aContro.abort()
    // 事件触发后,signal.aborted 变为 true
    alert(signal.aborted); // true
    

    结合 fetch:

    // 1 秒后中止
    let controller = new AbortController();
    setTimeout(() => controller.abort(), 1000);
    
    try {
      let response = await fetch('/article/fetch-abort/demo/hang', {
        signal: controller.signal
      });
    } catch(err) {
      if (err.name == 'AbortError') { // handle abort()
        alert("Aborted!");
      } else {
        throw err;
      }
    }
    

    Fetch Api

    fetch 还有很多 api,详见 现代教程 

    CORS 跨域

     "CORS" :跨源资源共享(Cross-Origin Resource Sharing)。

    JSONP

    JSONP 的原理很简单,就是利用一个 script 标签,然后我们的信息作为回调函数传过去来实现跨域:

    function gotWeather({ temperature, humidity }) {
      alert(`temperature: ${temperature}, humidity: ${humidity}`);
    }
    
    let script = document.createElement('script');
    script.src = `http://another.com/weather.json?callback=gotWeather`;
    document.body.append(script);
    
    

    简单跨域请求

    首先,简单跨域请求需要满足如下条件:

    • method —— GET,POST 或 HEAD
    • headers —— 仅允许自定义下列 header:
      • Accept,
      • Accept-Language,
      • Content-Language,
      • Content-Type 的为 application/x-www-form-urlencoded,multipart/form-data 或 text/plain。

    如果我们的跨域请求是一个简单请求,在发送 request 的时候,浏览器会自动添加 Origin header,服务器需要进行对 Origin 的检查,如果同意这个源的 request ,那么在 response 里就会添加一个 Access-Control-Allow-Origin 的响应头,这个响应头的内容可以是我们刚才的 Origin ,或者是一个 * 号 : JavaScript中的网络请求

    而且在这种简单请求中,我们只能访问简单的 response header :

    • Cache-Control
    • Content-Language
    • Content-Type
    • Expires
    • Last-Modified
    • Pragma

    其他的都不可访问

    非简单跨域请求

    不满足我们上面说的 request 条件的请求视为非简单请求,比如 methodPUT ,发送这样的请求的时候,浏览器会在发送正式请求前 加一个预检(preflight) 的请求

    预检请求使用 OPTIONS method,它没有 body ,但是有两个关键 header

    • Access-Control-Request-Method —— 正式请求中带有的非简单请求的 method 
    • Access-Control-Request-Headers —— 正式请求中带有的非简单的 HTTP-Header 列表,用 , 分隔

    而服务器端同意处理也会返回一个状态码200 ,没有 body 但是有一些特殊 header 的 response:

    • Access-Control-Allow-Origin 必须为 * 或进行请求的源(例如 [https://javascript.info](https://javascript.info))才能允许此请求。
    • Access-Control-Allow-Methods 必须具有允许的方法。
    • Access-Control-Allow-Headers 必须具有一个允许的 header 列表。
    • 另外,header Access-Control-Max-Age 可以指定缓存此权限的秒数。因此,浏览器不是必须为满足给定权限的后续请求发送预检。

    JavaScript中的网络请求  预检请求整个过程对于 JavaScript 是不可见的!JavaScript 仅仅获取主请求的 response 

    非简单跨域例子

    假设我们现在有一个跨域请求

    let response = await fetch('https://site.com/service.json', {
      method: 'PATCH',
      headers: {
        'Content-Type': 'application/json',
        'API-Key': 'secret'
      }
    });
    

    他的 method 、 headers 都不满足简单请求的规则,那么接下来会发生非简单请求:

    第一步 发送预检请求

    浏览器检测到非简单跨域请求后,会自动先发送一个预检

    OPTIONS /service.json
    Host: site.com
    Origin: https://javascript.info
    Access-Control-Request-Method: PATCH
    Access-Control-Request-Headers: Content-Type,API-Key
    

    包含了上面说的非简单请求的 method 和 非简单的 HTTP-Header 列表

    第二步 服务器响应预检请求

    200 OK
    Access-Control-Allow-Origin: https://javascript.info
    Access-Control-Allow-Methods: PUT,PATCH,DELETE
    Access-Control-Allow-Headers: API-Key,Content-Type,If-Modified-Since,Cache-Control
    Access-Control-Max-Age: 86400
    

    这个 response 表示:

    1. 请求通过 200 ,
    2. 允许的 Origin 是我们 request 的源,
    3. 他不仅允许我们 request 的 PATCH ,还允许 PUT/DELETE 等特殊 method ,当然 GET,POST 或 HEAD 本身就是允许的,response 不用写进去
    4. 也允许了一些 header : API-Key,Content-Type,If-Modified-Since,Cache-Control 
    5. 设置了缓存时间一天,后续的请求就不用再触发预检了

    第三步 发送实际请求

    通过了预检,自然是开始发送实际 requset,注意浏览器一定会加上 Origin header ,因为他是跨域的 

    PATCH /service.json
    Host: site.com
    Content-Type: application/json
    API-Key: secret
    Origin: https://javascript.info
    

    第四步 返回实际响应

    服务器返回的 response 也必须加上 Access-Control-Allow-Origin,即时我们已经通过了预检

    Access-Control-Allow-Origin: https://javascript.info
    

     至此一个非简单跨域完成

    凭据

    在上面的跨域情况中,无法发送任何 凭据(cookies 或者 HTTP 认证)  这是因为具有凭据的请求比没有凭据的请求要强大得多。如果被允许,它会使用它们的凭据授予 JavaScript 代表用户行为和访问敏感信息的全部权力。 如果需要发送凭据,需要加上 credentials: "include" :

    fetch('http://another.com', {
      credentials: "include"
    });
    

    而服务器如果同意接受带有凭据的请求,那么需要加上一个 header: Access-Control-Allow-Credentials: true ,当然也别忘记必须有的 Access-Control-Allow-Origin 

    200 OK
    Access-Control-Allow-Origin: https://javascript.info
    Access-Control-Allow-Credentials: true
    

    URL 对象

    以往我们打开一个窗口,可能会用如下方式:

    window.open("https://zh.javascript.info/url")
    

    这种方式,url 是一串字符串,而现在我们有一个内建的 URL 对象

    new URL(url, [base])
    

    如下两个 url 是一样的

    let url1 = new URL('https://javascript.info/profile/admin');
    let url2 = new URL('/profile/admin', 'https://javascript.info');
    
    alert(url1); // https://javascript.info/profile/admin
    alert(url2); // https://javascript.info/profile/admin
    

    也可以根据现有 URL 创建新 URL

    let url = new URL('https://javascript.info/profile/admin');
    let newUrl = new URL('tester', url);
    
    alert(newUrl); // https://javascript.info/profile/tester
    

    还可以访问他的组件属性

    let url = new URL('https://javascript.info/url');
    
    alert(url.protocol); // https:
    alert(url.host);     // javascript.info
    alert(url.pathname); // /url
    

    JavaScript中的网络请求 我们还可以处理 url 的参数:

    • append(name, value)  —— 按照 name 添加参数,
    • delete(name)  —— 按照 name 移除参数,
    • get(name)  —— 按照 name 获取参数,
    • getAll(name) —— 获取相同 name 的所有参数(这是可行的,例如 ?user=John&user=Pete),
    • has(name)  —— 按照 name 检查参数是否存在,
    • set(name, value)  —— set/replace 参数,
    • sort()  —— 按 name 对参数进行排序,很少使用,

    这些方法处理参数的时候,会自动进行编码 传统的编码一般是用这些方法:

    • encodeURI —— 编码整个 URL。
    • decodeURI —— 解码为编码前的状态。
    • encodeURIComponent —— 编码 URL 组件,例如搜索参数,或者 hash,或者 pathname。
    • decodeURIComponent —— 解码为编码前的状态。

    轮询

    短轮询

    短轮询就是定时地对服务器进行 request

    长轮询

    发送 request -> 消息挂起 -> 服务器 response 返回 -> 客户端处理 response -> 发起下一个 request

    async function subscribe() {
      let response = await fetch("/subscribe");
    
      if (response.status == 502) {
        // 状态 502 是连接超时错误,
        // 连接挂起时间过长时可能会发生,
        // 远程服务器或代理会关闭它
        // 让我们重新连接
        await subscribe();
      } else if (response.status != 200) {
        // 一个 error —— 让我们显示它
        showMessage(response.statusText);
        // 一秒后重新连接
        await new Promise(resolve => setTimeout(resolve, 1000));
        await subscribe();
      } else {
        // 获取并显示消息
        let message = await response.text();
        showMessage(message);
        // 再次调用 subscribe() 以获取下一条消息
        await subscribe();
      }
    }
    
    subscribe();
    

    WebSocket

    创建连接

    WebSocket  协议提供了一种在浏览器和服务器之间建立持久连接来交换数据的方法。数据可以作为“数据包”在两个方向上传递,而不会断开连接和其他 HTTP 请求。

    let socket = new WebSocket("ws://javascript.info");
    

    ws 是一个特殊的协议 ,还有一个 wss 协议,类似于 HTTPS 他是加密的,建议使用 wss:// 

    • open   —— 连接已建立,
    • message   —— 接收到数据,
    • error   —— WebSocket 错误,
    • close   —— 连接已关闭。
    let socket = new WebSocket("wss://javascript.info/article/websocket/demo/hello");
    
    socket.onopen = function(e) {
      alert("[open] Connection established");
      alert("Sending to server");
      socket.send("My name is John");
    };
    
    socket.onmessage = function(event) {
      alert(`[message] Data received from server: ${event.data}`);
    };
    
    socket.onclose = function(event) {
      if (event.wasClean) {
        alert(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`);
      } else {
        // 例如服务器进程被杀死或网络中断
        // 在这种情况下,event.code 通常为 1006
        alert('[close] Connection died');
      }
    };
    
    socket.onerror = function(error) {
      alert(`[error] ${error.message}`);
    };
    

    request

    以下是由 new WebSocket("wss://javascript.info/chat")  发出的请求的浏览器 header 示例。

    GET /chat
    Host: javascript.info
    Origin: https://javascript.info    [客户端页面的源]
    Connection: Upgrade		[Upgrade —— 表示客户端想要更改协议。]
    Upgrade: websocket		[websocket —— 请求的协议是 “websocket”]
    Sec-WebSocket-Key: Iv8io/9s+lYFgZWcXczP8Q==		[浏览器随机生成的安全密钥]
    Sec-WebSocket-Version: 13		[WebSocket 协议版本,当前为 13]
    

    这些 header 在JavaScript 中不可见。 还有 Sec-WebSocket-Protocol: soap, wamp 代表我们不仅要传输任何数据,还要传输 SOAP 或 WAMP 协议中的数据。这个是我们可设置的:

    let socket = new WebSocket("wss://javascript.info/chat", ["soap", "wamp"]);
    

    数据传输

    WebSocket 通信由 “frames”(即数据片段)组成,可以从任何一方发送,并且有以下几种类型:

    • “text frames” —— 包含各方发送给彼此的文本数据。
    • “binary data frames” —— 包含各方发送给彼此的二进制数据。
    • “ping/pong frames” 被用于检查从服务器发送的连接,浏览器会自动响应它们。
    • 还有 “connection close frame” 以及其他服务 frames。

    客户端请求 .send() 可以发送文本和二进制数据(包括 Blob,ArrayBuffer 等)

    接收到 response 的时候,文本肯定是字符串的形式,但是如果是个二进制数据,可以通过 socket.binaryType 来设置格式类型,默认值就是 blob 。

    socket.binaryType = "arraybuffer"; // 默认值是 blob 可以不设置
    socket.onmessage = (event) => {
      // event.data 可以是文本(如果是文本),也可以是 arraybuffer(如果是二进制数据)
    };
    

    缓冲数据

    假设我们一次发送了很多数据,但是网很慢,当我们反复 send() 的情况下,数据会缓存进内存, socket.bufferedAmount 可以查询当前缓存了多少字节

    // 每 100ms 检查一次 socket
    // 仅当所有现有的数据都已被发送出去时,再发送更多数据
    setInterval(() => {
      if (socket.bufferedAmount == 0) {
        socket.send(moreData());
      }
    }, 100);
    

    关闭连接

    不管是客户端还是服务端都可以主动关闭连接,当需要关闭的时候:

    socket.close(code,reason)
    
    • code —— 一个关闭码:
      • 1000 —— 默认,正常关闭(如果没有指明 code 时使用它),
      • 1006 —— 没有办法手动设定这个数字码,表示连接丢失(没有 close frame)。
      • 1001 —— 一方正在离开,例如服务器正在关闭,或者浏览器离开了该页面,
      • 1009 —— 消息太大,无法处理,
      • 1011 —— 服务器上发生意外错误
    • reason —— 关闭原因,比如 “work complete”

    我们可以在 close 事件中获取他们: event.code / event.reason

    连接状态

    要获取连接状态,可以通过带有值的 socket.readyState 属性:

    • 0 —— “CONNECTING”:连接还未建立,
    • 1 —— “OPEN”:通信中,
    • 2 —— “CLOSING”:连接关闭中,
    • 3 —— “CLOSED”:连接已关闭。

    起源地下载网 » JavaScript中的网络请求

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元