Redux Saga async/await pattern
Asked Answered
C

4

61

I'm using async/await throughout my codebase. Because of this my api calls are defined by async functions

async function apiFetchFoo {
  return await apiCall(...);
}

I would like to call this function from my saga code. It seems like I can not do this:

// Doesn't work
function* fetchFoo(action) {
  const results = await apiFetchFoo();
  yield put({type: "FOOS_FETCHED_SUCCESSFULLY", foos: results});
}

However, this does work, and matches the redux saga documentation:

// Does work
function* fetchFoo(action) {
  const results = yield call(apiFetchFoo);
  yield put({type: "FOOS_FETCHED_SUCCESSFULLY", foos: results});
}

Is this the correct way to use Redux Saga alongside async/await? It is standard to use this generator syntax inside of the saga code, and the async/await pattern elsewhere?

Cerenkov answered 17/4, 2017 at 0:37 Comment(2)
Curious, in async/await pattern why are you using function *() { ... await } instead of async function () { .. await ...}? I'm pretty sure if you use await without async it leads to an error "await is a reserved javascript keyword".Taper
Small side note: if you're using TypeScript and typedefs and you define the function as an asynchronous function like so: async function* fetchFoo(action: requestAction): AsyncGenerator {, you need to include "es2018.asynciterable" to your tsconfig's compilerOptions.lib array if you haven't already. Also, I needed TypeScript 3.7 (from 3.0).Farah
S
57

Yes, that's the standard way to use Redux-Saga.

You should never be calling the await function directly inside the saga-generator, because redux-saga is for orchestrating the side-effects. Therefore, any time that you want to run a side-effect you should do it by yielding the side-effect through a redux-saga effect (usually: call or fork). If you do it directly without yielding it through a redux-saga effect, then redux-saga won't be able to orchestrate the side-effect.

If you think about it, the redux-saga generator is completely testable without the need of mocking anything. Also, it helps to keep things decoupled: if your apiFetchFoo returned a promise, the saga would still work the same.

Secretarial answered 17/4, 2017 at 2:42 Comment(5)
Can you include any links to documentation or github issues where this is discussed? What if there was a situation where the OP wanted to await a Promise that had no side effects? It seems like it would be a valid use case for async generators, but it doesn't seem like Redux-Saga allows for them.Workingman
@Workingman sure! redux-saga.js.org I am afraid that you don't quite understand what redux-saga is about. A redux-saga generator is meant for orchestrating side-effects. If you want to do something like what you are mentioning, then you shouldn't do it in a redux-saga generator... You could do it in a normal generator -if you want- or in another promise, it doesn't matter... and then just yield a call effect inside the redux-saga generator that calls that promise/generator/whatever.Secretarial
Ah yes, that makes sense! I think I was misunderstanding the boundaries of a redux-saga generator, and was trying to make it do more than it should. Instead, I should await the "no side-effect" promise in the entity that is being yielded into the saga generator.Workingman
Just for reference (may help others), here is good explanation from react-saga documentation of why we should not invoke asynchronous functions directly in saga: redux-saga.js.org/docs/basics/DeclarativeEffects.html. As per this documentation, saga should yield effects to saga middleware and saga middleware will execute the effect and will hand over the result back to saga. Effects are simple JS objects telling middleware, what to do. This whole idea of yielding effects to saga middleware make unit-testing of saga simple, without mocking async things like API calls.Salad
Lovely: the redux-saga generator is completely testable without the need of mocking anythingOfficeholder
E
7

As pointed out by Josep, await cannot be used inside a generator. Instead you need to use an async function. Also, note this is a limitation of async function itself. It is not imposed by redux-saga.

Beyond this, I also wanted to mention that it is a conscious choice by the redux-saga authors to not allow devs to express sagas as async/await functions.

Generators are more powerful than async/await and they allow advanced features of redux-saga like co-ordinating parallel tasks.

Moreover, expressing sagas as generators help us define Effects which are plain objects defining the side effect. Effects makes it very easy to test our sagas.

So, although your working code is fine, maybe not mixing up sagas and async function is a good idea.

Just define your apiFetchFoo to return a promise which resolves with the response to the request. And when this happens your saga will resume with the results.

 const apiFetchFoo = () =>
   fetch('foo')
     .then(res => res.json())
Enclitic answered 13/4, 2018 at 17:22 Comment(0)
A
0

As the previous answers says , Redux saga uses side effects which handles the async within it so the way of doing it , Is using yield and call if calling an API and so on so far

Avrom answered 2/6, 2022 at 6:24 Comment(0)
S
-14

await always work within a function that's declared as async. #thumbRule

async function fetchList () {
  let resp = await fetchApi([params]);
}
Sogdiana answered 19/11, 2017 at 7:1 Comment(1)
Sorry but that is not always the case.Dedal

© 2022 - 2024 — McMap. All rights reserved.