redux-logic icon indicating copy to clipboard operation
redux-logic copied to clipboard

call done automatically

Open lstkz opened this issue 7 years ago • 6 comments

Is it possible to call the done callback automatically, if the process function is executed or the returned promise is resolved? I am revamping my current app to use redux-logic, and this is a very common use case for me.

export const someLogic = createLogic({
  type: SOME_ACTION,
  async process({apiClient}, dispatch, done) {
    const data = await apiClient.getSomeData();
    if (data.something) {
      done();
      return;
    }
    // do more stuff here
    done();
  },
});

It could be converted to this:

export const someLogic = createLogic({
  type: SOME_ACTION,
  autoDone: true,
  async process({apiClient}, dispatch, done) {
    const data = await apiClient.getSomeData();
    if (data.something) {
      return;
    }
    // do more stuff here
  },
});

Such syntax would be much useful for me because there is always a risk to forget to call done().

lstkz avatar May 11 '17 10:05 lstkz

Not sure if this exactly answers your question, but there is a way to auto-dispatch. See processOptions.

I don't know how it would work with async/await, though since I've not been using it yet. But as I understand it, your return can be a Promise, Generator, or an RXJS Observable.

jktravis avatar May 11 '17 13:05 jktravis

Such syntax would be much useful for me because there is always a risk to forget to call done().

I've found automatic linting (with eslint-loader in webpack, or as separate lint step in CI) helpful in such cases. If I alway use the process({...}, dispatch, done) 3-argument style and miss a done() call, then eslint will complain loud and clear if no-unused-vars config is set :)

./src/app/logic.js
  90:45  warning  'done' is defined but never used  no-unused-vars

erkiesken avatar May 11 '17 14:05 erkiesken

@lsentkiewicz Thanks for the question. As @jktravis mentioned you can return a promise (or result from async await) or an observable.

It works great with async/await as you can imagine.

export const someLogic = createLogic({
  type: SOME_ACTION,
  async process({apiClient}) { // by excluding dispatch and done, we will wait for result
    const data = await apiClient.getSomeData();
    if (data.something) {
      return success(data.something); // return action you want to dispatch
    }
    // do more stuff here
  },
});

If you want to define the action type or action creator outside of the logic then you can use processOptions.successType to specify the type or creator to decorate the data with before dispatching.

export const someLogic = createLogic({
  type: SOME_ACTION,
  processOptions: {
    successType: success, // type or action creator for success
    failType: fail // type or action creator for failures
  },
  async process({apiClient}) { // by excluding dispatch and done, we will wait for result
    const data = await apiClient.getSomeData();
    if (data.something) {
      return data.something; // return action you want to dispatch
    }
    // do more stuff here
  },
});

I have some examples that demonstrate. Look for await or processOptions ones.

jeffbski avatar May 11 '17 14:05 jeffbski

@jeffbski I need to dispatch multiple times. processOptions work only for a single dispatch.

@tehnomaag I am using ESLint in my project, but it's not a solution for this problem. In my above example, I call done() twice. If I forget to call done in one place, eslint won't report it.

lstkz avatar May 11 '17 14:05 lstkz

Yeah the processOptions way doesn't allow for multiple dispatch calls.

One way to get async/await style, multiple dispatch and only do one done call is to use Bluebird.finally like so:

export const testLogic = createLogic({
    type: "testingAsyncAwait",
    process({ action }, dispatch, done) {
        Bluebird
            .resolve(async function () {
                dispatch({ type: "test1" });
                const data = await someDataFetch();
                dispatch({ type: "test2", payload: data });
            }())
            .finally(done);
    },
});

erkiesken avatar May 11 '17 15:05 erkiesken

Or creating a wrapper

function autoDoneWrapper(process) {
  return (params, dispatch, done) => {
    Bluebird
      .resolve(process(params, dispatch))
      .finally(done);
  };
}

export const testLogic = createLogic({
  type: "testingAsyncAwait",
  process: autoDoneWrapper(async ({ action }, dispatch) => {
    dispatch({type: "test1"});
    const data = await someDataFetch();
    dispatch({type: "test2", payload: data});
  }),
});

lstkz avatar May 16 '17 09:05 lstkz