メインコンテンツにスキップ

トラブルシューティング

Sagaを追加するとアプリがフリーズする

ジェネレーター関数からエフェクトを yield していることを確認してください。

次の例を考えてみましょう

import { take } from 'redux-saga/effects'

function* logActions() {
while (true) {
const action = take() // wrong
console.log(action)
}
}

これは、take() がエフェクトの記述を作成するだけであるため、アプリケーションを無限ループに陥れます。ミドルウェアが実行するために yield しない限り、while ループは通常の while ループのように動作し、アプリケーションがフリーズします。

yield を追加すると、ジェネレーターが一時停止し、Redux Saga ミドルウェアに制御が戻り、エフェクトが実行されます。take() の場合、Redux Saga はパターンに一致する次のアクションを待機し、その後にのみジェネレーターを再開します。

上記の例を修正するには、take() によって返されるエフェクトを yield します。

import { take } from 'redux-saga/effects'

function* logActions() {
while (true) {
const action = yield take() // correct
console.log(action)
}
}

Sagaがディスパッチされたアクションを欠落させている

Sagaが何らかのエフェクトでブロックされていないことを確認してください。Sagaがエフェクトの解決を待機している場合、エフェクトが解決されるまでディスパッチされたアクションを受け取ることができません。

たとえば、次の例を考えてみましょう

function* watchRequestActions() {
while (true) {
const { url, params } = yield take('REQUEST')
yield call(handleRequestAction, url, params) // The Saga will block here
}
}

function* handleRequestAction(url, params) {
const response = yield call(someRemoteApi, url, params)
yield put(someAction(response))
}

watchRequestActionsyield call(handleRequestAction, url, params) を実行すると、次の yield take に進む前に、handleRequestAction が終了して戻るまで待機します。たとえば、次のようなイベントシーケンスがあるとします。

UI                     watchRequestActions             handleRequestAction
-----------------------------------------------------------------------------
.......................take('REQUEST').......................................
dispatch(REQUEST)......call(handleRequestAction).......call(someRemoteApi)... Wait server resp.
.............................................................................
.............................................................................
dispatch(REQUEST)............................................................ Action missed!!
.............................................................................
.............................................................................
.......................................................put(someAction).......
.......................take('REQUEST')....................................... saga is resumed

上記のように、Sagaがブロッキング呼び出しでブロックされている場合、その間にディスパッチされたすべてのアクションを見逃します。

Sagaのブロックを回避するには、call の代わりに fork を使用して、非ブロッキング呼び出しを使用できます。

function* watchRequestActions() {
while (true) {
const { url, params } = yield take('REQUEST')
yield fork(handleRequestAction, url, params) // The Saga will resume immediately
}
}

ルートSagaにバブリングするエラーのエラースタックが読みにくい

Saga内のタスクは本質的に非同期であるため、同期呼び出しのチェーンであるかのように「Sagaスタック」を表示するために、追加の作業を行う必要があります。そのため、redux-saga@v1 以降では、エラーがルートSagaにバブリングすると、ライブラリはその「Sagaスタック」を構築し、onError コールバックの2番目の引数のプロパティ sagaStack: string として渡します(ミドルウェアオプションも参照)。これにより、エラー追跡システムに送信したり、その他の追加作業を行うことができます。

結果として、コンソールに次のようなものが表示されます。

saga-error-stack.png

開発目的で、ファイル名と行番号を含むこれらの「Sagaスタック」が必要な場合は、babel-plugin を追加できます。これにより、拡張された情報を得ることができます。ドキュメントはこちらで入手できます。babel-pluginの使用例については、この例を確認してください。

babel-plugin-redux-saga を追加すると、同じ出力は次のようになります。

saga-error-stack-with-babel-plugin.png

注:テストでも同様に動作します。sagaをsagaMiddleware経由で実行することを確認してください(またはランナー)。

saga-error-stack-node.png