日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產(chǎn)品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Fluent Fetcher: 重構基于 Fetch 的 JavaScript 網(wǎng)絡請求庫

源代碼地址:這里

在第一版本的 Fluent Fetcher 中,筆者希望將所有的功能包含在單一的 FluentFetcher 類內,結果發(fā)現(xiàn)整個文件冗長而丑陋;在團隊內部嘗試推廣時也無人愿用,包括自己過了一段時間再拾起這個庫也覺得很棘手。在編寫 declarative-crawler 的時候,筆者又用到了 fluent-fetcher,看著如亂麻般的代碼,我不由沉思,為什么當時會去封裝這個庫?為什么不直接使用 fetch,而是自找麻煩多造一層輪子??蚣鼙旧硎菍τ趶陀么a的提取或者功能的擴展,其會具有一定的內建復雜度。如果內建復雜度超過了業(yè)務應用本身的復雜度,那么引入框架就不免多此一舉了。而網(wǎng)絡請求則是絕大部分客戶端應用不可或缺的一部分,縱觀多個項目,我們也可以提煉出很多的公共代碼;譬如公共的域名、請求頭、認證等配置代碼,有時候需要添加擴展功能:譬如重試、超時返回、緩存、Mock 等等。筆者構建 Fluent Fetcher 的初衷即是希望能夠簡化網(wǎng)絡請求的步驟,將原生 fetch 中偏聲明式的構造流程以流式方法調用的方式提供出來,并且為原有的執(zhí)行函數(shù)添加部分功能擴展。

那么之前框架的問題在于:

  • 模糊的文檔,很多參數(shù)的含義、用法包括可用的接口類型都未講清楚;
  • 接口的不一致與不直觀,默認參數(shù),是使用對象解構(opt = {})還是函數(shù)的默認參數(shù)(arg1, arg2 = 2);
  • 過多的潛在抽象漏洞,將 Error 對象封裝了起來,導致使用者很難直觀地發(fā)現(xiàn)錯誤,并且也不便于使用者進行個性化定制;
  • 模塊獨立性的缺乏,很多的項目都希望能提供盡可能多的功能,但是這本身也會帶來一定的風險,同時會導致最終打包生成的包體大小的增長。

好的代碼,好的 API 設計確實應該如白居易的詩,淺顯易懂而又韻味悠長,沒有人有義務透過你邋遢的外表去發(fā)現(xiàn)你美麗的心靈。開源項目本身也意味著一種責任,如果是單純地為了炫技而提升了代碼的復雜度卻是得不償失。筆者認為最理想的情況是使用任何第三方框架之前都能對其源代碼有所了解,像 React、Spring Boot、TensorFlow 這樣比較復雜的庫,我們可以慢慢地撥開它的面紗。而對于一些相對小巧的工具庫,出于對自己負責、對團隊負責的態(tài)度,在引入之前還是要了解下它們的源碼組成,了解有哪些文檔中沒有提及的功能或者潛在風險。筆者在編寫 Fluent Fetcher 的過程中也參考了 OkHttp、super-agent、request 等流行的網(wǎng)絡請求庫。

基本使用

V2 版本中的 Fluent Fetcher 中,最核心的設計變化在于將請求構建與請求執(zhí)行剝離了開來。RequestBuilder 提供了構造器模式的接口,使用者首先通過 RequestBuilder 構建請求地址與配置,該配置也就是 fetch 支持的標準配置項;使用者也可以復用 RequestBuilder 中定義的非請求體相關的公共配置信息。而 execute 函數(shù)則負責執(zhí)行請求,并且返回經(jīng)過擴展的 Promise 對象。直接使用 npm / yarn 安裝即可:

 
 
 
  1. npm install fluent-fetcher 
  2.  
  3. or 
  4.  
  5. yarn add fluent-fetcher 

創(chuàng)建請求

基礎的 GET 請求構造方式如下:

 
 
 
  1. import { RequestBuilder } from "../src/index.js"; 
  2.  
  3. test("構建完整跨域緩存請求", () => { 
  4.   let { url, option }: RequestType = new RequestBuilder({  
  5.       scheme: "https", 
  6.       host: "api.com", 
  7.       encoding: "utf-8" 
  8.   }) 
  9.     .get("/user") 
  10.     .cors() 
  11.     .cookie("*") 
  12.     .cache("no-cache") 
  13.     .build({ 
  14.       queryParam: 1, 
  15.       b: "c" 
  16.     }); 
  17.  
  18.   chaiExpect(url).to.equal("https://api.com/user?queryParam=1&b=c"); 
  19.  
  20.   expect(option).toHaveProperty("cache", "no-cache"); 
  21.  
  22.   expect(option).toHaveProperty("credentials", "include"); 
  23. }); 

RequestBuilder 的構造函數(shù)支持傳入三個參數(shù):

 
 
 
  1. * @param scheme http 或者 https 
  2. * @param host 請求的域名 
  3. * @param encoding 編碼方式,常用的為 utf8 或者 gbk 

然后我們可以使用 header 函數(shù)設置請求頭,使用 get / post / put / delete / del 等方法進行不同的請求方式與請求體設置;對于請求體的設置是放置在請求方法函數(shù)的第二與第三個參數(shù)中:

 
 
 
  1. // 第二個參數(shù)傳入請求體 
  2. // 第三個參數(shù)傳入編碼方式,默認為 raw json 
  3. post("/user", { a: 1 }, "x-www-form-urlencoded") 

最后我們調用 build 函數(shù)進行請求構建,build 函數(shù)會返回請求地址與請求配置;此外 build 函數(shù)還會重置內部的請求路徑與請求體。鑒于 Fluent Fetch 底層使用了 node-fetch,因此 build 返回的 option 對象在 Node 環(huán)境下僅支持以下屬性與擴展屬性:

 
 
 
  1.     // Fetch 標準定義的支持屬性 
  2.     method: 'GET', 
  3.     headers: {},        // request headers. format is the identical to that accepted by the Headers constructor (see below) 
  4.     body: null,         // request body. can be null, a string, a Buffer, a Blob, or a Node.js Readable stream 
  5.     redirect: 'follow', // set to `manual` to extract redirect headers, `error` to reject redirect 
  6.  
  7.     // node-fetch 擴展支持屬性 
  8.     follow: 20,         // maximum redirect count. 0 to not follow redirect 
  9.     timeout: 0,         // req/res timeout in ms, it resets on redirect. 0 to disable (OS limit applies) 
  10.     compress: true,     // support gzip/deflate content encoding. false to disable 
  11.     size: 0,            // maximum response body size in bytes. 0 to disable 
  12.     agent: null         // http(s).Agent instance, allows custom proxy, certificate etc. 

此外,node-fetch 默認請求頭設置:

HeaderValueAccept-Encodinggzip,deflate (when options.compress === true)Accept*/*Connectionclose (when no options.agent is present)Content-Length(automatically calculated, if possible)User-Agentnode-fetch/1.0 (+https://github.com/bitinn/node-fetch)

請求執(zhí)行

execute 函數(shù)的說明為:

 
 
 
  1. /** 
  2.  * Description 根據(jù)傳入的請求配置發(fā)起請求并進行預處理 
  3.  * @param url 
  4.  * @param option 
  5.  * @param {*} acceptType json | text | blob 
  6.  * @param strategy 
  7.  */ 
  8.  export default function execute( 
  9.   url: string, 
  10.   option: any = {}, 
  11.   acceptType: "json" | "text" | "blob" = "json", 
  12.   strategy: strategyType = {} 
  13. ): Promise{} 
  14.  
  15. type strategyType = { 
  16.   // 是否需要添加進度監(jiān)聽回調,常用于下載 
  17.   onProgress: (progress: number) => {}, 
  18.  
  19.   // 用于 await 情況下的 timeout 參數(shù) 
  20.   timeout: number 
  21. }; 

引入合適的請求體

默認的瀏覽器與 Node 環(huán)境下我們直接從項目的根入口引入文件即可:

 
 
 
  1. import {execute, RequestBuilder} from "../../src/index.js"; 

默認情況下,其會執(zhí)行 require("isomorphic-fetch"); ,而在 React Native 情況下,鑒于其自有 fetch 對象,因此就不需要動態(tài)注入。譬如筆者在CoderReader 中 獲取 HackerNews 數(shù)據(jù)時,就需要引入對應的入口文件

 
 
 
  1. import { RequestBuilder, execute } from "fluent-fetcher/dist/index.rn"; 

而在部分情況下我們需要以 Jsonp 方式發(fā)起請求(僅支持 GET 請求),就需要引入對應的請求體:

 
 
 
  1. import { RequestBuilder, execute } from "fluent-fetcher/dist/index.jsonp"; 

引入之后我們即可以正常發(fā)起請求,對于不同的請求類型與請求體,請求執(zhí)行的方式是一致的:

 
 
 
  1. test("測試基本 GET 請求", async () => { 
  2.   const { url: getUrl, option: getOption } = requestBuilder 
  3.     .get("/posts") 
  4.     .build(); 
  5.  
  6.   let posts = await execute(getUrl, getOption); 
  7.  
  8.   expectChai(posts).to.have.length(100); 
  9. }); 

需要注意的是,部分情況下在 Node 中進行 HTTPS 請求時會報如下異常:

 
 
 
  1. (node:33875) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): FetchError: request to https://test.api.truelore.cn/users?token=144d3e0a-7abb-4b21-9dcb-57d477a710bd failed, reason: unable to verify the first certificate 
  2. (node:33875) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. 

我們需要動態(tài)設置如下的環(huán)境變量:

 
 
 
  1. process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; 

自動腳本插入

有時候我們需要自動地獲取到腳本然后插入到界面中,此時就可以使用 executeAndInject 函數(shù),其往往用于異步加載腳本或者樣式類的情況:

 
 
 
  1. import { executeAndInject } from "../../src/index"; 
  2.  
  3. let texts = await executeAndInject([ 
  4.   "https://cdn.jsdelivr.net/fontawesome/4.7.0/css/font-awesome.min.css" 
  5. ]); 

筆者在 create-react-boilerplate 項目提供的性能優(yōu)化模式中也應用了該函數(shù),在 React 組件中我們可以在 componentDidMount 回調中使用該函數(shù)來動態(tài)加載外部腳本:

 
 
 
  1. // @flow 
  2. import React, { Component } from "react"; 
  3. import { message, Spin } from "antd"; 
  4. import { executeAndInject } from "fluent-fetcher"; 
  5.  
  6. /** 
  7.  * @function 執(zhí)行外部腳本加載工作 
  8.  */ 
  9. export default class ExternalDependedComponent extends Component { 
  10.   state = { 
  11.     loaded: false 
  12.   }; 
  13.  
  14.   async componentDidMount() { 
  15.     await executeAndInject([ 
  16.       "https://cdnjs.cloudflare.com/ajax/libs/Swiper/3.3.1/css/swiper.min.css", 
  17.       "https://cdnjs.cloudflare.com/ajax/libs/Swiper/3.3.1/js/swiper.min.js" 
  18.     ]); 
  19.  
  20.     message.success("異步 Swiper 腳本加載完畢!"); 
  21.  
  22.     this.setState({ 
  23.       loaded: true 
  24.     }); 
  25.   } 
  26.  
  27.   render() { 
  28.     return ( 
  29.        
  30.         {this.state.loaded 
  31.           ?  
  32.               Swiper 
  33.                
  34.                 Swiper 加載完畢,現(xiàn)在你可以在全局對象中使用 Swiper! 
  35.               

     
  36.               
  37.                 height="504px" 
  38.                 width="320px" 
  39.                 src="http://img5.cache.netease.com/photo/0031/2014-09-20/A6K9J0G94UUJ0031.jpg" 
  40.                 alt="" 
  41.               /> 
  42.             
 
  •           : 
     
  •                
  •             
  •        
  •     ); 
  •   } 
  • 代理

    有時候我們需要動態(tài)設置以代理方式執(zhí)行請求,這里即動態(tài)地為 RequestBuilder 生成的請求配置添加 agent 屬性即可:

     
     
     
    1. const HttpsProxyAgent = require("https-proxy-agent"); 
    2.  
    3. const requestBuilder = new RequestBuilder({ 
    4. scheme: "http", 
    5. host: "jsonplaceholder.typicode.com" 
    6. }); 
    7.  
    8. const { url: getUrl, option: getOption } = requestBuilder 
    9. .get("/posts") 
    10. .pathSegment("1") 
    11. .build(); 
    12.  
    13. getOption.agent = new HttpsProxyAgent("http://114.232.81.95:35293"); 
    14.  
    15. let post = await execute(getUrl, getOption,"text"); 

    擴展策略

    中斷與超時

    execute 函數(shù)在執(zhí)行基礎的請求之外還回為 fetch 返回的 Promise 添加中斷與超時地功能,需要注意的是如果以 Async/Await 方式編寫異步代碼則需要將 timeout 超時參數(shù)以函數(shù)參數(shù)方式傳入;否則可以以屬性方式設置:

     
     
     
    1. describe("策略測試", () => { 
    2.   test("測試中斷", done => { 
    3.     let fnResolve = jest.fn(); 
    4.     let fnReject = jest.fn(); 
    5.  
    6.     let promise = execute("https://jsonplaceholder.typicode.com"); 
    7.  
    8.     promise.then(fnResolve, fnReject); 
    9.  
    10.     // 撤銷該請求 
    11.     promise.abort(); 
    12.  
    13.     // 異步驗證 
    14.     setTimeout(() => { 
    15.       // fn 不應該被調用 
    16.       expect(fnResolve).not.toHaveBeenCalled(); 
    17.       expect(fnReject).toHaveBeenCalled(); 
    18.       done(); 
    19.     }, 500); 
    20.   }); 
    21.  
    22.   test("測試超時", done => { 
    23.     let fnResolve = jest.fn(); 
    24.     let fnReject = jest.fn(); 
    25.  
    26.     let promise = execute("https://jsonplaceholder.typicode.com"); 
    27.  
    28.     promise.then(fnResolve, fnReject); 
    29.  
    30.     // 設置超時 
    31.     promise.timeout = 10; 
    32.  
    33.     // 異步驗證 
    34.     setTimeout(() => { 
    35.       // fn 不應該被調用 
    36.       expect(fnResolve).not.toHaveBeenCalled(); 
    37.       expect(fnReject).toHaveBeenCalled(); 
    38.       done(); 
    39.     }, 500); 
    40.   }); 
    41.  
    42.   test("使用 await 下測試超時", async done => { 
    43.     try { 
    44.       await execute("https://jsonplaceholder.typicode.com", {}, "json", { 
    45.         timeout: 10 
    46.       }); 
    47.     } catch (e) { 
    48.       expectChai(e.message).to.equal("Abort or Timeout"); 
    49.     } finally { 
    50.       done(); 
    51.     } 
    52.   }); 
    53. }); 

    進度反饋

     
     
     
    1. function consume(reader) { 
    2.   let total = 0; 
    3.   return new Promise((resolve, reject) => { 
    4.     function pump() { 
    5.       reader.read().then(({done, value}) => { 
    6.         if (done) { 
    7.           resolve(); 
    8.           return 
    9.         } 
    10.         total += value.byteLength; 
    11.         log(`received ${value.byteLength} bytes (${total} bytes in total)`); 
    12.         pump() 
    13.       }).catch(reject) 
    14.     } 
    15.     pump() 
    16.   }) 
    17.  
    18. // 執(zhí)行數(shù)據(jù)抓取操作 
    19. fetch("/music/pk/altes-kamuffel.flac") 
    20.   .then(res => consume(res.body.getReader())) 
    21.   .then(() => log("consumed the entire body without keeping the whole thing in memory!")) 
    22.   .catch(e => log("something went wrong: " + e)) 

    Pipe

    execute 還支持動態(tài)地將抓取到的數(shù)據(jù)傳入到其他處理管道中,譬如在 Node.js 中完成圖片抓取之后可以將其保存到文件系統(tǒng)中;如果是瀏覽器環(huán)境下則需要動態(tài)傳入某個 img 標簽的 ID,execute 會在圖片抓取完畢后動態(tài)地設置圖片內容:

     
     
     
    1. describe("Pipe 測試", () => { 
    2.   test("測試圖片下載", async () => { 
    3.     let promise = execute( 
    4.       "https://assets-cdn.github.com/images/modules/logos_page/Octocat.png", 
    5.       {}, 
    6.       "blob" 
    7.     ).pipe("/tmp/Octocat.png", require("fs")); 
    8.   }); 
    9. }); 

     【本文是專欄作者“張梓雄 ”的原創(chuàng)文章,如需轉載請通過與作者聯(lián)系】

    戳這里,看該作者更多好文


    網(wǎng)站題目:Fluent Fetcher: 重構基于 Fetch 的 JavaScript 網(wǎng)絡請求庫
    分享路徑:http://m.5511xx.com/article/cdhejhe.html