redux-saga: Saga triggered in yield map loop only executes once
Really enjoying redux-saga, but I’m running into a problem while using yield myArray.map
. I have simplified the sagas from my app to illustrate:
export function * listCamerasFlow () {
while (true) {
const {deviceId} = yield take(CAMERA_LIST_REQUEST)
let cameras = [{ id: '1', deviceId }, { id: '2', deviceId }]
yield put(cameraListReady(cameras))
yield cameras.map(camera => put(cameraSnapshotRequest(camera.deviceId, camera.id)))
console.log('CAMERAS', cameras)
}
}
export function * cameraSnapshotFlow () {
while (true) {
const request = yield take(CAMERA_SNAPSHOT_REQUEST)
console.log('GOT REQUEST', request)
// yield put(cameraSnapshotReady('yo'))
console.log('DONE')
}
}
export function * root () {
yield fork(listCamerasFlow)
yield fork(cameraSnapshotFlow)
}
And here are the action creators:
export const cameraSnapshotRequest = (deviceId, cameraId) => {
console.log('ACTION', cameraId, deviceId)
return {
type: CAMERA_SNAPSHOT_REQUEST,
deviceId,
cameraId
}
}
export const cameraSnapshotReady = (imageURL) => ({
type: CAMERA_SNAPSHOT_READY,
imageURL
})
When CAMERA_LIST_REQUEST
comes in, the following is logged to the console. Note that GOT REQUEST
and DONE
are logged twice (as expected):
ACTION 1 644ffd5d-ee45-45dd-95ef-7efc5b6218f1
ACTION 2 644ffd5d-ee45-45dd-95ef-7efc5b6218f1
GOT REQUEST Object {type: "app/CAMERA_SNAPSHOT_REQUEST", deviceId: "644ffd5d-ee45-45dd-95ef-7efc5b6218f1", cameraId: "1"}
DONE
GOT REQUEST Object {type: "app/CAMERA_SNAPSHOT_REQUEST", deviceId: "644ffd5d-ee45-45dd-95ef-7efc5b6218f1", cameraId: "2"}
DONE
CAMERAS [Object, Object]
However, if I uncomment yield put(cameraSnapshotReady('yo')
:
export function * cameraSnapshotFlow () {
while (true) {
const request = yield take(CAMERA_SNAPSHOT_REQUEST)
console.log('GOT REQUEST', request)
yield put(cameraSnapshotReady('yo')) // <---- added this line
console.log('DONE')
}
}
I now get the following logged to the console:
ACTION 1 644ffd5d-ee45-45dd-95ef-7efc5b6218f1
ACTION 2 644ffd5d-ee45-45dd-95ef-7efc5b6218f1
GOT REQUEST Object {type: "app/CAMERA_SNAPSHOT_REQUEST", deviceId: "644ffd5d-ee45-45dd-95ef-7efc5b6218f1", cameraId: "1"}
CAMERAS [Object, Object]
DONE
Notice that GOT REQUEST
and DONE
are only logged one time. The second put
within yield cameras.map
is called since ACTION 1
shows up. But cameraSnapshotFlow
seems to only get one take
. Also, DONE
comes after CAMERAS
in the second scenario.
Is there something simple I’m doing wrong? Any ideas why this is not working?
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Comments: 15 (11 by maintainers)
I marked this as a bug. I’ll try to ‘fix’ the scheduler to make it handle this case (maybe possible). Otherwise we have to document this case and possible workarounds. but I prefer to delay the 2nd solution until we make sure this can’t be fixed in the scheduler.
Since JavaScript is a single threaded language, everything have to run in sequence. But redux-saga schedules puts internally by ‘executing’ them one after another. ‘executing’ means that when we execute a
put
, we dont allow anotherput
to execute until we exhaust the effects of the currently executingput
.In your case, when you yield the array of puts, the first put in the array gets executed atomically (i.e. will delay other puts yielded during its execution). Since your
cameraSnapshotFlow
is issuing a put itself in the middle of the execution of the 1stlistCamerasFlow ...-> cameras.map(...)
put, it will be delayed until all pending puts execute. This means thecameraSnapshotFlow
will be suspended until the puts of thecameras.map(...)
array execute. which means it can’t take the 2nd put.Why do we delay the
cameraSnapshotFlow
’s put? ImaginelistCamerasFlow
want to take actions fromcameraSnapshotFlow
when
listCamerasFlow
executes the put effect. ThecameraSnapshotFlow
reaction will execute inside the same stack frame of the put. it means when we resume toyield take(CAMERA_SNAPSHOT_READY)
theCAMERA_SNAPSHOT_READY
action has already been dispatched when we were executing the previous put effect. redux-saga delays nested put effects in order to make it possible for them to be taken later like in this case.Of course in your case
listCamerasFlow
is not interested in taking anything fromcameraSnapshotFlow
but the presence/absence of an interdependence can not be inferred by the library.Yes,
actionChannel
is the correct way to solve the problem ortakeEvery
(depending on a use case).put
s are scheduled with internalscheduler
so nested tasks etc are handled correctly.The problem lies here that when 1st
put
is being handledtake
reacts to it, meetsput
which is queued internally, not resolved immediately sotake
could be met again, so in the moment of 2ndput
(from the map) the worker (saga) you wanted to w8 for the dispatched action didnt come to itstake
again.