schedule2021-08-31

axiosでリクエスト中の処理をキャンセルする

Node.js (electron)で作成していたアプリでHTTPリクエスト中の処理をキャンセルして再度リクエストなどする必要があったため、Axiosでリクエスト中の処理をキャンセルする実装をしました。

リクエストを出した側からキャンセルすることができます。

Axios

Axiosはnode.jsとブラウザのためのpromiseベースのHTTPクライアント。

キャンセル処理についてはドキュメントのCancellationに書いてあります。

コードサンプルと解説

ドキュメントのコードサンプルです。

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/user/12345', {
  // キャンセルのトークン
  cancelToken: source.token
}).catch(function (thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
    // handle error
  }
});

axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token
})

// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');

CancelToken.sourceを利用してキャンセルトークンを作成してリクエストのオプションに設定します。 そしてsource.cancel()でリクエストをキャンセルできる。

CancelToken.source()が返すsourceは独立しているので、▼のようにリクエスト別にキャンセルできます。

// キャンセルトークンが独立している
const source1 = CancelToken.source();
const source2 = CancelToken.source();

axios.get('/user/12345', {
  cancelToken: source1.token
})
axios.get('/user/12345', {
  cancelToken: source2.token
})

動かして確認してみる

確認しやすいようにドキュメントのコードサンプルを拡張しました。 http://localhost:3000/delayが時間のかかる処理のエンドポイントとしています。

const axios = require('axios');
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
const source2 = CancelToken.source();

const start = Date.now();
console.log("start: ", start)

// ①レスポンスがそんなかからないリクエスト
axios.get('http://localhost:3000/', {
  cancelToken: source.token
}).then((res) => {
    // 正常動作
    console.log(Date.now() - start, res.data);
}).catch((thrown) => {
  if (axios.isCancel(thrown)) {
    console.log(Date.now() - start, 'Request canceled: ', thrown.message);
  } else {
    // handle error
    console.log(Date.now() - start, 'Some error: ', thrown.message);
  }
})

// ②レスポンスに3秒かかるリクエスト
axios.get('http://localhost:3000/delay', {
  cancelToken: source.token
}).then((res) => {
    // 正常動作
    console.log(Date.now() - start, res.data);
}).catch((thrown) => {
  if (axios.isCancel(thrown)) {
    console.log(Date.now() - start, 'Request canceled: ', thrown.message);
  } else {
    // handle error
    console.log(Date.now() - start, 'Some error: ', thrown.message);
  }
})


// ③レスポンスに3秒かかるリクエスト(キャンセルしない)
axios.get('http://localhost:3000/delay', {
  cancelToken: source2.token
})
.then((res) => {
    // 正常動作
    console.log(Date.now() - start, res.data);
}).catch((thrown) => {
  if (axios.isCancel(thrown)) {
    console.log(Date.now() - start, 'Request canceled: ', thrown.message);
  } else {
    // handle error
    console.log(Date.now() - start, 'Some error: ', thrown.message);
  }
})

// 1秒後にキャンセルリクエスト
setTimeout(() => {
    // cancel the request (the message parameter is optional)
    source.cancel('Operation canceled by the user.');
}, 1000);

3つのリクエストを用意してキャンセルしています。

  • ①レスポンスがそんなかからないリクエスト
  • ②キャンセルするレスポンスに3秒かかるリクエスト
  • ③キャンセルしないレスポンスに3秒かかるリクエスト

これを実行すると▼のログが出ます。数字はミリ秒です。

> node .\cancellation.js
start:  1630385223319
71 Hello World!
1021 Request canceled:  Operation canceled by the user.
3081 3 seconds delay
  • ①はキャンセル前にリクエストが終了しているため正常終了。
  • ②は3秒かかる処理の途中でキャンセルが実行されたことがわかる。
  • ③はキャンセルしたトークンと別のキャンセルトークンが設定されていたため、キャンセルされず正常終了しました。

サーバ側のコード

リクエストキャンセルの確認のためのサーバをExpressで再現しました。 http://localhost:3000/delayが時間のかかる処理のエンドポイントとしています。

const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
  res.send('Hello World!')
})

// リクエスト返答まで時間がかかるエンドポイント
app.get('/delay', (req, res) => {
  setTimeout(()=>{
    res.send('3 seconds delay');
  }, 3000);
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

コードサンプルはGithubに登録しています。