問題
ReduxなどのFluxアプリケーションの実装では、アクションのフォーマットとして、 Flux Standard Action(FSA) を採用しているプロジェクトも多いと思います。 FSAを使って非同期アクションの結果を表現しようとしているときに、以下のような疑問を抱いたことはないでしょうか?
たとえば、TODOアイテムの更新をするアクションとして、UPDATE_TODO:REQUESTED
, UPDATE_TODO:RECEIVED
という2つのアクションがあるとします。これらは、それぞれHTTPリクエストとレスポンスに対応します。
// リクエスト
{
type: 'UPDATE_TODO:REQUESTED',
payload: {
id: 100,
text: 'Do something!'
}
}
// レスポンス
{
type: 'ADD_TODO:RECEIVED',
payload: {
id: 100,
text: 'Do something!'
}
}
リクエスト成功時は、これで問題ありません。
では、エラーレスポンスが返ってきたときはどうなるでしょうか?
FSAにおいては、error
プロパティーが true
の場合、payload
はエラーオブジェクトであるべきと定められています。
JavaScriptのエラーオブジェクトは、 Errorコンストラクタ で表現されます。
{
type: 'ADD_TODO:RECEIVED',
payload: error, // error instanceof Error === true
error: true
}
問題は、複数のTODOの更新操作が並行して走る場合です。リクエストエラー時には、ユーザーに対して操作が完了しなかったことを通知したいでしょう。複数の更新操作が同時に走るのであれば、どのアイテムに対する操作が失敗したのかも示したほうが親切かもしれません。
しかし、この場合、payload
は Error
オブジェクトに占有されてしまっています。エラー時に、複数の操作を識別するためには、どうすれば良いでしょうか?
解法: カスタムエラーオブジェクトを作成する
ひとつの方法として、カスタムエラーオブジェクトを作成することが考えられます。
class IdentifiableError extends Error {
constructor(id, ...args) {
super(...args)
this.name = 'IdentifiableError'
this.targetId = id
}
}
このように専用のエラークラスを定義し、リクエストエラー時には、このオブジェクトを payload
に格納します。
エラーオブジェクトの targetId
プロパティーを参照すれば、どの項目についての操作が失敗したのかを識別できます。
また、静的型環境での型付けも問題ありません。
別の解法1: エラー時専用のアクションを用意する
アクションとして、UPDATE_TODO:REQUESTED
、UPDATE_TODO:RECEIVED
の2種類ではなく、UPDATE_TODO:REQUESTED
、UPDATE_TODO:SUCCEEDED
, UPDATE_TODO:FAILED
の3種類にします。
そして、FAILEDの場合には、error !== true
の通常のアクションとして、payload
に非エラーオブジェクトを乗せます。
別の解法2: metaに情報を持たせる
FSAでは、type
、 payload
、 error
以外の第4のフィールドとして、meta
プロパティーを持つことが許されています。
meta
プロパティーにも payload
と同様に任意のオブジェクトを乗せられるため、これを使って解決することも可能です。
ただ、いまここで乗せたい識別情報は、meta
に乗せるような情報なのかは、判断が難しいところです。
更新対象のIDというのは、どちらかというと、ペイロードそのもののようにも思えます。
ミドルウェアで、アクションごとのユニークなIDを発行して、それをmetaに乗せて、TODOアイテムと関連付けた上でリクエストの状態を管理するというような、よりシステマチックで大掛りな方法も考えられるかもしれません。
参考リンク
- What is the reason error property is boolean? #17 FSAのリポジトリでこの問題について議論されています。
- Custom JavaScript Errors in ES6 カスタムエラーオブジェクトの作成方法が解説されています。