あらすじ
2019年8月10日。私はVue.jsとDjango REST Frameworkを使い、ある業務システムの画面開発を担当していた。
フレームワークなんて代物はまだ触り始めて2,3か月。とは言えどこを触れば何が変わるか多少は理解してきている自負もあった。
そんな良くも悪くも弾みのついていた私を戒めるかのように、単体テスト中、そいつは突然現れた。
出会い
『更新中の…ステータスが…… エラーコード:409』
「(動作確認では何度も正常に作動していた機能開発...そんなはずは...)」
NG判定とエラー内容をテスト記録として記述する。
"""
HTTP 409 Conflict はリクエストが現在のサーバーの状態と競合したことを示すステータスコード。
競合は PUT メソッドを使用したリクエストのレスポンスで最も発生しやすい。例えば、サーバーにすでに存在しているファイルよりも古いバージョンのファイルをアップロードした際に409の応答が返され、バージョン管理システムの競合が発生する可能性がある。
"""
409 Conflict
困った困った。なけなしの知識と経験であれやこれやと思考した。
そして画面の再描画後、再度そいつは現れた。
『更新中の…ステータスが…… エラーコード:409』
非同期処理の沼
「ここか。」
答えに辿り着くのに長くはかからなかった。
// post(url, data, loading)
// url : 呼び出し先のURL
// data : REST側に送りたいデータ
// loading : 処理終了までローディング画面を表示
// 重ための処理
private async heavyProcess(): Promise<void> {
const payload = '画面から送る何かのデータ群';
this.$http.post(url='hoge-url1', data={'payload': payload}, loading=false);
this.$http.post(url='hoge-url2', data={'payload': payload}, loading=false);
// 画面の更新処理
this.onShown();
}
ボタンを押下して、この処理が走っている間に他の作業も進められるようにしたい。数十秒~数分なんて流石に待っていられない。
そのような声は当然にあり、この処理は上記の通り非同期処理で実装していた。
ここでの2つのhttp.post(以下、処理①と処理②)は類似処理を行っているが、具体的には下記の処理を行っている。
1.画面内のデータを取得しREST側へPOST
2.取得したデータの条件が合えば、外部APIへ有料の関連データを取得
3.種々のCRUD処理を行う
更にエラーの発生パターンについて調べたところ、大きく下記の2つであった。
1.処理①が上記1~3を終えて、戻り値を返して処理②を行おうとした直後
2.処理①のみor処理②のみが走るように画面側で予め条件設定し、1度目の処理を終えた直後に同じ処理のトリガーを引いた直後
つまり、処理量の多さにCRUD処理が実際には完了しきいっていない状態の時に再び処理①②を行うことでエラーが起きていた。
それを示すように awaitを付記するとloading画面は処理終了まで続くがエラーは消える。(当然っちゃ当然だが)
// post(url, data, loading)
// url :
// data : 何かのデータ群
// loading : 処理終了までローディング画面を表示
// 重ための処理
private async heavyProcess(): Promise<void> {
const payload = '画面から送る何かのデータ群';
await this.$http.post(url='hoge-url1', data={'payload': payload}, loading=false);
await this.$http.post(url='hoge-url2', data={'payload': payload}, loading=false);
// 画面の更新処理
this.onShown();
}
とにかく、今回のエラーの発生要因やその背景なんかを整理すると
・外部APIが有料ということもあり、連続or多数の本チャンデータ取得パターンの確認を怠った。
・1度のボタン押下で行う非同期処理を2つに分けてしまった。
(※片方は他画面で使用しているViewに飛んでいた為、このような実装になった)
・REST側で各処理を行うViewを呼び出す為の、共通クラスを用意しなかった。
(※やってることは2個目と同じ)
しかし、上記の課題を全てクリアして当初の設計通り実装しようとしても、まだエラーの発生パターン2が解決しない。
そう、ユーザーが短時間に連続で同じ処理を行おうとした時だ。完全に非同期処理の沼に嵌まってしまったのである。
2019年8月11日。27歳の誕生日を迎えた今日、未だ解決策は見つかっていない。
To Be Continued
後日談
2019年8月16日。夏季休暇も空け、リードエンジニアの先輩に相談したところ、解決策らしきものはあった。只、ユーザビリティの向上が見込まれるとは言え、スケジュールの都合もあり今回の実装は断念するに至った。非同期処理は諦めて、上述したようにawaitを追加した次第である。
ちなみにその解決策だが、then()関数を用いて、非同期処理が失敗または完了するまで該当のボタン押下時処理を行えなくするというものだった。詳しくはこちらのサイトを参照されたし。