1. 程式人生 > >axios妙用技巧

axios妙用技巧

axios妙用技巧

前言

應用開發中,各服務的呼叫使用最多的就是HTTP的形式,使用的HTTP client也從request --> superagent --> axios。axios中的各類函式都是基於promise的形式,雖然我也鍾情於superagent的鏈式呼叫,但axios的各類promisetransforminterceptor等特性,只能擁抱無法拒絕~

create

建立一個新的例項,此例項的公共配置獨立與其它例項。一般在後端開發會經常需要對接各類不同的服務,而各服務使用單獨的例項是較合理的方法,如下面例子初始化一個用於呼叫百度服務的例項:

const axios = require('axios');

const baiduService = axios.create({
  // 設定介面路徑(相對路徑將拼接此路徑)
  baseURL: 'https://www.baidu.com/',
  // 根據不同的應用設定預設的超時
  timeout: 3 * 1000,
});

async function main() {
  try {
    const res = await baiduService.get('/');
    console.info(res.status);
  } catch (err) {
    console.error(err);
  }
}
main();

transformRequest

在傳送請求前,可以對傳送的資料做轉換處理,預設的transformRequest中會將提交的資料轉換為對應的字串(json或者querystring),具體程式碼可檢視transformRequest

我的應用中有一個統計服務,使用的是批量傳送統計指標(設定為每次傳送200個指標),對頻寬的佔用較大,因此希望傳送指標時做資料壓縮,下面看看怎麼針對需求實現自定義的transform

const axios = require('axios');
const zlib = require('zlib');

const localService = axios.create({
  baseURL: 'http://127.0.0.1:3000/',
  timeout: 3 * 1000,
  transformRequest: [
    // 複用原有的轉換,實現json --> json string
    axios.defaults.transformRequest[0],
    (data, header) => {
      // 如果已處理過資料,則跳過
      if (!header['Content-Encoding']) {
        return data;
      }
      // 如果資料長度1KB(如字元資料並不一定小於1KB),不壓縮
      if (data.length < 1024) {
        return data;
      }
      // 將資料壓縮(可根據需要,只壓縮長度大於多少的資料)
      // 設定資料型別
      header['Content-Encoding'] = 'gzip';
      const w  = zlib.createGzip();
      w.end(Buffer.from(data));
      return w;
    },
  ],
});

async function main() {
  try {
    const arr = [];
    for (let index = 0; index < 100; index++) {
      // 模擬生成統計資料
      arr.push({
        category: 'login',
        account: 'vicanso',
        value: Math.round(Math.random() * 100),
        ip: '127.0.0.1',
      });
    }
    const res = await localService.post('/', {
      data: arr,
    });
    console.info(res.status);
  } catch (err) {
    console.error(err);
  }
}
main();

服務端程式碼:

const Koa = require('koa');
const Router = require('koa-router');
const bodyParser = require('koa-bodyparser');

const app = new Koa();
const router = new Router();


// body parser中可以解壓資料
// 如果希望支援再多型別的壓縮資料,可參考https://github.com/stream-utils/inflation調整
app.use(bodyParser());

router.post('/', async (ctx) => {
  console.dir(ctx.request.body);
  ctx.body = 'OK';
});

app
  .use(router.routes())
  .use(router.allowedMethods());

app.listen(3000);

通過上面的自定義gzip的transform,頻寬的佔用節約了70%左右,當然這裡會增加了CPU的損耗,根據各自的應用場景選擇不壓縮或者使用snappy等壓縮速度優先的演算法。

transformResponse

在接收到響應資料時,可以對響應資料做轉換處理,預設的transform是呼叫JSON.parse轉換為對應的Object。其的使用方法與transformRequest類似,不再舉例細說。

adapter

可實現自定義的請求處理,axios實現了基於瀏覽器的xhr以及nodejs的兩種處理,使其適應於兩種執行環境。一般我們不需要自己去實現adapter,主要的使用場景是在測試中mock資料,如下:

const axios = require('axios');

const baiduService = axios.create({
  // 設定介面路徑(相對路徑將拼接此路徑)
  baseURL: 'https://www.baidu.com/',
  // 根據不同的應用設定預設的超時
  timeout: 3 * 1000,
});


function mockAdapter(ins, fn) {
  const {
    adapter,
  } = ins.defaults;
  ins.defaults.adapter = fn;
  return () => {
    ins.defaults.adapter = adapter;
  };
}

async function main() {
  const done = mockAdapter(baiduService, (config) => {
    // mock response,只返回狀態碼與data
    return Promise.resolve({
      status: 200,
      data: 'OK',
    });
  });
  try {
    const res = await baiduService.get('/');
    // 恢復adapter
    console.info(res.status);
  } catch (err) {
    console.error(err);
  } finally {
    done();
  }
}
main();

http(s)Agent

指定在nodejs環境中的http(s)的agent,如maxSockets,timeout等。下面例子中啟用keepAlive,複用TCP連線,提升效能(預設是未啟用)。

const axios = require('axios');
const http = require('http');
const https = require('https')

const localService = axios.create({
  baseURL: 'http://127.0.0.1:3000/',
  timeout: 3 * 1000,
  httpAgent: new http.Agent({
    keepAlive: true,
  }),
  httpsAgent: new https.Agent({
    keepAlive: true
  }),
});

async function main() {
  try {
    // 兩次順序呼叫,複用同樣的tcp連線
    let res = await localService.get('/');
    console.info(res.status);
    res = await localService.get('/');
    console.info(res.status);
  } catch (err) {
    console.error(err);
  }
}
main();

服務端的程式碼,展示是否使用同一TCP連線:

const Koa = require('koa');
const Router = require('koa-router');

const app = new Koa();
const router = new Router();


router.get('/', async (ctx) => {
  // 生成socke id,用於標記TCP連線
  if (!ctx.socket._id) {
    ctx.socket._id = Math.floor(Math.random() * 1000);
  }
  console.info(ctx.socket._id);
  ctx.body = 'OK';
});

app
  .use(router.routes())
  .use(router.allowedMethods());

app.listen(3000);

Interceptors

Interceptorsaxios的一大特色,使用攔截器可以對傳送請求、接收資料做各類的操作(非同步的也支援)。如請求重試,前置認證等等。

request interceptor

後端服務部署,為了高可用,避免單點故障,一般而言都會部署多節點。各服務之間的呼叫,簡單的方式則是使用nginx或haproxy之類做反向代理,應用程式只接入反向代理的節點,這樣簡單方便,實際上反向代理則成為單點,達不到高可用的目標(實際情況對於大部分公司,訪問量不大,反向代理穩定,基本也不出狀況)。下面我們來討論如果在客戶端實現高可用的接入方式(如有完善的微服務體系,接入sidecar更簡單便捷,無程式碼入侵性):

const axios = require('axios');

class Backends {
  constructor(backends) {
    this.backends = backends.map((url) => {
      return {
        url,
        healthy: false,
      };
    });
  }
  // 選擇其中可用的backend
  get(policy) {
    let backend = null;
    switch (policy) {
      case 'first':
        this.backends.forEach((item) => {
          if (!backend && item.healthy) {
            backend = item;
          }
        });
        break;
      // 可實現更多的選擇策略,如round robin等
      default:
        break;
    } 
    return backend;
  }
  doHealthCheck() {
    // 可以根據需要調整為更完善的檢測方法,
    // 如檢測5次,3次通過則認為healthy
    this.backends.forEach((backend) => {
      axios.get(`${backend.url}/ping`).then((res) => {
        const {
          status,
        } = res;
        if (status === 200) {
          backend.healthy = true;
        } else {
          backend.healthy = false;
        }
      }).catch(() => {
        backend.healthy = false;
      })
    });
  }
  startHealthCheck() {
    setInterval(() => {
      this.doHealthCheck();
    }, 5000).unref();
    this.doHealthCheck();
  }
}

const localServiceBackends = new Backends([
  'http://127.0.0.1:3000',
  'http://127.0.0.1:3001',
]);
localServiceBackends.startHealthCheck();

const localService = axios.create({
  timeout: 3 * 1000,
});

localService.interceptors.request.use((config) => {
  const backend = localServiceBackends.get('first');
  if (!backend) {
    return Promise.reject(new Error('無可用的服務'))
  }
  config.baseURL = backend.url;
  return config;
})

async function main() {
  try {
    const res = await localService.get('/');
    console.info(res.status);
  } catch (err) {
    console.error(err);
  }
}

// 延時執行,等待首次health check
setTimeout(main, 1000);

服務端程式碼:

const Koa = require('koa');
const Router = require('koa-router');

const app = new Koa();
const router = new Router();


router.get('/', async (ctx) => {
  ctx.body = 'OK';
});

router.get('/ping', (ctx) => {
  ctx.body = 'pong';
});

app
  .use(router.routes())
  .use(router.allowedMethods());

app.listen(3000);

response interceptor

函式調用出錯統一基於Error物件擴充套件,後端各服務都限定了標準的出錯返回,以JSON的形式返回出錯資料{"message": "出錯資訊", ...},其中message則是出錯資訊,因此需要調整axios以相容介面的出錯響應(預設返回的Error.message為http狀態碼的描述)。

const axios = require('axios');

const localService = axios.create({
  baseURL: 'http://127.0.0.1:3000/',
  timeout: 3 * 1000,
});

localService.interceptors.response.use(null, (err) => {
  if (err.response && err.response.data) {
    const {
      data,
    } = err.response;
    if (data.message) {
      // 可以根據後端出錯資料的標準,往error中新增再多的屬性
      err.message = data.message;
    }
  }
  return Promise.reject(err);
});

async function main() {
  try {
    await localService.get('/');
  } catch (err) {
    console.error(err.message);
  }
}
main();

服務端程式碼:

const Koa = require('koa');
const Router = require('koa-router');

const app = new Koa();
const router = new Router();

// 公共的出錯處理
app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    ctx.status = err.status || 500;
    ctx.body = {
      message: err.message,
    };
  }
});

router.get('/', async (ctx) => {
  ctx.throw(400, '出錯了')
});

router.get('/ping', (ctx) => {
  ctx.body = 'pong';
});

app
  .use(router.routes())
  .use(router.allowedMethods());

app.listen(3000);

介面分析

組合使用requestresponseinterceptors,可以無入侵式的增加介面分析,如效能、介面響應、資料等統計分析。

const axios = require('axios');

const localService = axios.create({
  baseURL: 'http://127.0.0.1:3000/',
  timeout: 3 * 1000,
});


const stats = (response) => {
  // 未考慮各類異常場景
  const {
    config,
  } = response;
  const {
    method,
    url,
    _start,
  } = config;
  // 可輸出更多的引數,如post資料,響應資料等
  console.info(`${method} ${url} ${Date.now() - _start}ms status:${response.status}`);
};

localService.interceptors.request.use((config) => {
  config._start = Date.now();
  return config;
});

localService.interceptors.response.use((response) => {
  stats(response);
}, (err) => {
  stats(err.response);
  return Promise.reject(err);
});

async function main() {
  try {
    await localService.post('/');
    await localService.post('/error');
  } catch (err) {
    console.error(err.message);
  }
}
main();

服務端程式碼:

const Koa = require('koa');
const Router = require('koa-router');

const app = new Koa();
const router = new Router();

// 公共的出錯處理
app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    ctx.status = err.status || 500;
    ctx.body = {
      message: err.message,
    };
  }
});

router.post('/', async (ctx) => {
  ctx.body = {
    foo: 'bar',
  };
});
router.post('/error', async (ctx) => {
  ctx.throw(400, '出錯啦');
});

router.get('/ping', (ctx) => {
  ctx.body = 'pong';
});

app
  .use(router.routes())
  .use(router.allowedMethods());

app.listen(3000);

小結

在認真瞭解axios之前,我一直不解為什麼其star的數量這麼高,比superagent高了那麼多,當時自己沒去研究,理所當然認為因為vue推薦使用它,所以才那麼火,深入瞭解之後,發現它的確有著過人之處。不要讓自己的見識誤解世界,要以實踐瞭解世界。

相關推薦

axios技巧

axios妙用技巧 前言 應用開發中,各服務的呼叫使用最多的就是HTTP的形式,使用的HTTP client也從request -

位運算的技巧

C/C++語言提供的位運算子有: 運算子 含義 功能 & 按位與 兩個二進位制位都為1,則該位的結果值為1;否則為0。 | 按位或 兩個二進位制

Delphi三層開發小技巧:TClientDataSet的Delta

lena 維護 data 查詢語句 self source 結果 one 通用方法 Delphi做三層開發時,很多人都會在客戶端放一個TClientDataSet,中間層遠程數據模塊就對應放一個TDataSetProvider,然後再連起來.其實這種方法很煩瑣,而且程序癰腫

【Linux除錯技巧----標準輸出重定向到檔案】dup2和dup的

dup和dup2都可用來複制一個現存的檔案描述符,使兩個檔案描述符指向同一個file結構體。如果兩個檔案描述符指向同一個file結構體,File Status Flag和讀寫位置只儲存一份在file結構體中,並且file結構體的引用計數是2。如果兩次open同一檔案得到兩個檔案描述符,則每個描述符對應一個

Visual Studio 除錯技巧之即時視窗的

在 Visual Studio 中有一個視窗叫 **Immediate** 視窗,中文版本應該叫**即時視窗**。預設會在你啟動除錯時在 VS 編輯器中彈出來。你也可以通過 `Debug | Windows | Immediate` 或者使用快捷鍵 `Ctrl+Alt+I` 手動把它調出來。 ![](htt

循環的

要掌握 lin while循環 inpu 步驟 過程 猜想 有時 技術   當我們在學習程序設計的過程中,我們會發現有很多重復的步驟,變化的僅僅是當中的某一個變量,這就要引入我們的一個重要的知識——“循環”。循環就是重復執行語句,這是個很方便又很有意思的技術,可以重復操作任

Python:eval的和濫用

list 建議 表達式 博文 環境 dem 完整 定期 mat eval()函數十分強大,官方demo解釋為:將字符串str當成有效的表達式來求值並返回計算結果。 so,結合math當成一個計算器非常好用。 其它使用方法,能夠把list,tuple

css實現梯形(各種形狀)的網頁布局——transform的

建議 logs 網頁布局 mar 500px 實現 hidden .html order      在各式各樣的網頁中,經常會看到形狀特別的布局,比如說下面的這種排版方式:   這種視覺上的效果,體驗十分好。那麽他是如何來實現的呢

深入淺出 Javascript中apply、call、bind

com alt apply all 如何使用 name 深入 期待 單體模式   網上文章雖多,大多復制粘貼,且晦澀難懂,我希望能夠通過這篇文章,能夠清晰的提升對apply、call、bind的認識,並且列出一些它們的妙用加深記憶。  apply、call   在 ja

KMP的(利用next數組尋找字符串的循環節)

images 關系 其中 一次 容易 line 數組 最大 最小 利用KMP的next數組的性質,我們可以找到next數組的循環節。先說結論:設字符串長n,則若其 i % ( i – next[n] ) == 0 ,則其有循環節(循環節數目大於1),其循環節數目為 i /

#include &quot;*.c&quot;文件的

fcm 每次 weight 不同 int 文件夾 保存 程序設計 必須 今天我在看代碼的時候突然看到在一個.c文件裏包括了#include "*.c"代碼,這個讓我非常詫異,然後google了一下。才發現是這麽回事情。以下我寫了一個測試代碼。

font-szie=0的

真的 src 樣式 不讓 nbsp lin image lock cnblogs p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px ".PingFang SC"; color: #454545 } p.p2 { mar

C語言在linux內核中do while(0)之法

pos turn jsb world div fprintf cpp efault code 為什麽說do while(0) 妙?由於它的確就是妙,並且在linux內核中實現是相當的妙,我們來看看內核中的相關代碼: #define db_error(fmt, ..

集合的收集

其中 blog 語言 插入 目的 bcd odi ise fse 1)字典: 有序性 collections模塊: 1.OrderedDict: 有序字典 2.defaultdict: 帶有默認值的字典 OrderedDict: 使用dict時,Key是無序的。在對d

C語言中do...while(0)的-避免goto

java ng- pop execute size 釋放資源 and var text 使用goto的優雅並避免結構的混亂 將要跳轉到的語句用do{…}while(0) 包起來就可以。 reference #defien N 10 bool

python with 語句

body 原理 value 協議 span print int ger world class aa(): def bb(self): print("hhhh") return "hello world" de

關於setTimeout的

param 我們 als 分塊 csr tle 能夠 更新 val 定義 在指定的延遲時間之後調用一個函數或執行一個代碼片段 這個是setTimeout最主要的功能,但也是很坑的地方,首先javascript其實是運行在單線程的環境下,意味者定時器會在未來的某個時間支持,但

c# 擴展方法奇思基礎篇五:Dictionary<TKey, TValue> 擴展

ews public turn false div role 自我 cnblogs static Dictionary<TKey, TValue> 類是常用的一個基礎類,但用起來有時確不是很方便。本文逐一討論,並使用擴展方法解決。 向字典中添加鍵和值 添加鍵和值

Javascript:字符串分割split()

clas png toolbar new plain date rest 小明 空字符 轉載:http://www.cnblogs.com/kevinCoder/p/4554960.html Javascript:字符串分割split()妙用 概述: split()

Git Bash的 - 使用Linux命令

win32 ron 最小 可執行文件 es2017 都是 輸入 版本控制工具 原理 如何在Windows中使用Linux命令?   網上有很多說是安裝CygwinPortable 在cmd 窗口下是用linux 命令,但是還有一些缺陷。   其實對於程序員來說有一個非常