🪓

axios 知見集(2017)

2021/04/18に公開

request処理をするときに使い勝手が良くて気にっているaxiosをそこそこ使うようになって溜まった知見。


1. adapterを使って簡易mocking

リクエスト関連は結構テストが辛くなりがち。
このあたりを解決する方法として、nockなどリクエスト系をサードパーティ化するのがよくあるが、
axiosの場合、adapter機能を利用するとmockを簡単につくれる

const axios = require('axios')

const mockData = [{
  id: 1,
  title: 'Some Article',
}]

const mockAdapter = (config) => {
  return new Promise((resolve, reject) => {
    resolve({data: mockData, status: 200 })
  })
}

axios.get('/some/url', {
  adapter: mockAdapter
}).then( response => {
  console.log(response.data) // mockData
  console.log(response.status) // 200
})

自前したくない場合はmoxiosとかaxios-mock-adapterとかもある。

2. 基本configを固定するやり方 ( axios.create(), axios.defaults)

例えば様々なAPIで同じような設定をしたいことがよくあると思う。

axios.get('/some/url', { 
  xsrfHeaderName: 'X-CSRF-Token',
  withCredentials: true
})
axios.post('/some/other/url',{foo: 'bar'}, { 
  xsrfHeaderName: 'X-CSRF-Token',
  withCredentials: true
})

このような場合には、axios.create()を利用して別インスタンスとして生成してしまうのが良い

const myHttpClient = axios.create({ 
  xsrfHeaderName: 'X-CSRF-Token',
  withCredentials: true
})
myHttpClient.get('/some/url') // 設定が反映されてリクエストがトブ

また、別な方法として、globalな設定としてdefaultsで設定することも可能だ

axios.defaults.withCredentials = true
axios.defaults.xsrfHeaderName =  'X-CSRF-Token'

timeoutなど共通的な設定で十分なものだけで済むなら使って良さそうだろう。

defaults機能はaxiosが影響を受けたangular1の$httpに由来するものと思われる。
angularのようにシステムでひとまとめにするフレームワーク上では都合が良いことが多かったと想像出来るが、axiosで利用するシーンはそうではない場合が多いと想像出来るので、個人的にはaxios.createのほうがおすすめできる

3. 処理差し込み

3-1. interceptorsで処理差し込み

例えばnormalizrと併用して、API結果を変換した状態にしたいならこんな具合に利用出来る。

// ユーザーデータを変換するAPI
const userApi = axios.create()

userApi.interceptors.response.use( (response) => {
  response.data = normalize(res.data, article)
  // response.dataを上書きしたくなければ、下記の用にしても良いだろう
  // response.normalized = normalize(res.data, article)
  return res
})

userApi.get('/foo')
  .then( r => console.log(r.data)) // normalizedしたデータが取れる

interceptorはrequest,responseが用意されている。
useは複数追加することも出来るので、何か処理をわけたいなら下記のようにも書ける

// request
userApi.interceptors.request.use( (config) => {
  // configには送信前のdataやurlとかが入っている
    :
  return config
})

// response
userApi.interceptors.response.use( (response) => {
  /* 処理ひとつめ */
    :
  return response
})
userApi.interceptors.response.use( (response) => {
  /* 処理ふたつめ */
    :
  return response
})

また、interceptをejectする機能も用意されている

var myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);

3-2. transformResponse / transformRequestで処理差し込み

transformResponse / transformRequest はそれぞれinterceptよりもリクエストに近いタイミングで処理される。
transformRequestは送信の形式json以外の特殊な場合などに利用すると良いだろう。

transformResponseは主に文字化け対策などで利用できる

// 文字化け対処の例
axios(url, {
  responseType: "arraybuffer",
  transformResponse: [ (data) => {
    return iconv.convert(data, 'EUCJP', 'UTF8').toString()
  }]
})

おまけ:それぞれどういう順序で行われるのか検証する

こんなスクリプトで調べる

const axios = require('axios')

const api = axios.create({
  adapter: function(config){
    console.log("adapter")
    return new Promise((resolve, reject) => {
      console.log("adapter(promise)")
      resolve({data: config.data})
    })
  },
  transformRequest: [(data, header) => {
    console.log("transformRequest")
    return data
  }],
  transformResponse: [(data) => {
    console.log("transformResponse")
    return data
  }]
})

api.interceptors.request.use((config) => {
  console.log("interceptors.request")
  return config
})
api.interceptors.response.use((response) => {
  console.log("interceptors.response")
  return response
})
api.post('/foo', { mock: "data"})

結果がこちら

$ node axios-interceptor.js
interceptors.request
transformRequest
adapter
adapter(promise)
transformResponse
interceptors.response

interceptor => transform => adapterという形で処理が行われている事がわかる。

4. 複数APIの並列処理 (axios.all, axios.spread)

axios.allspreadを利用して並列処理に扱える。

const axios = require('axios')
const api = axios.create()

axios.all([
  api.get('/my/api'),
  api.get('/my/api2')
]).then( axios.spread( (api1Result, api2Result) => {
  //
})

但し、axios.allPromise.allのラッパー、axios.spreadは配列を引数化しているだけなので、ES2015のDestructuring構文を組み合わせると実はそちらでも十分出来てしまう。

const axios = require('axios')
const api = axios.create()

Promise.all([
  api.get('/my/api'),
  api.get('/my/api2')
]).then( ([api1Result, api2Result]) => {
  //
})

まとめ

  • 色々使い方覚えると、結構使い勝手が変わるのでオススメ
    • 特にaxios.createinterceptorsなどは結構違う
  • その他にもCancel機能やらform形式での送信など結構READMEに色々書いてある

Discussion