JavaScript では非同期処理が可能で API にアクセスする時などにしばしばでてきます。この非同期処理について有名な注意点として呼出し順と完了順が異なる場合があるという点があります。これは例えば次のコードで確認できます。
// 同期 Array(10).fill(null).forEach((_, i) => console.log(i)) // 非同期 Array(10).fill(null).forEach((_, i) => setTimeout(() => console.log(i), Math.random()*5))
どちらも 1,2,..,10 となりそうですが、非同期側は数字昇順になりません。これは各処理の処理完了までの時間がまちまちだからです。

非同期処理にはこの様な呼出し順と完了順の不一致という注意点がありますが最もよく使われているであろう非同期処理である API との通信(少なくとも自分は最もよく使っています)でこの注意点による問題が現れることはまあまあ珍しいです。というのも通信が走るのは大体人の手でなんらかの操作が行われた時であり、人の手による操作速度は通信の順序を変えるほど早くないことがほとんどだからです。とはいえ通信環境など何らかの要因により非同期処理の処理完了順が変わる時は確かにあります。この状態をテストしたくあります。この記事では axios でこれをテストする方法を紹介します。
コードとデモが次です。
/**
* JavaScript でマイクロ秒待機する
* @see https://leben.mobi/blog/how_to_implement_sleep/javascript/
*/
function sleep(time) {
return new Promise(function(resolve, reject) {
window.setTimeout(resolve, time);
});
}
/**
* axios の通信前処理として実行する関数を生成する関数。
* ランダムな秒数待つ。
* @param {Number} msMax 待ち時間最大値ミリ秒
* @param {Number} msMin 待ち時間最小値ミリ秒
*/
function makeDelayAxios(msMax, msMin = 0) {
return async (config) => {
await sleep(Math.random() * (msMax - msMin) + msMin);
return config;
};
}
// axios を生成して interceptors.request に追加
const aAxios = axios.create();
aAxios.interceptors.request.use(makeDelayAxios(2000));
const bAxios = axios.create();
bAxios.interceptors.request.use(makeDelayAxios(2000));
// デモ用通信処理
document.getElementById('runner').addEventListener('click',()=>{
const aMsgEl = document.getElementById('a');
const bMsgEl = document.getElementById('b');
aMsgEl.innerText ='running';
bMsgEl.innerText ='running';
// 通信を実行
aAxios.get('/').finally(() => aMsgEl.innerText ='finish a');
bAxios.get('/').finally(() => bMsgEl.innerText ='finish b');
// finish b -> finish a と表示される時と
// finish a -> finish b と表示される時の二種類が起こりうるようになる
});
axios の通信前処理の中にランダム時間の待ち処理を埋め込むことによってランダム遅延を起こします。前処理を追加できる interceptors.request については次に説明があります。
axios/axios: Promise based HTTP client for the browser and node.js#interceptors
また interceptors を用いずとも次の様に通信完了後の処理の前に sleep の待ちを挟むことで疑似的に通信完了を遅延させられます。
aAxios
.get('/')
.then(async () => {
await sleep(2000);
// 処理本体
})
.catch(async () => {
await sleep(2000);
// 処理本体
})
.finally(() => (aMsgEl.innerText = 'finish a'));