Dva: Model: How to "wait" for more/group of saga put calls in effects

Created on 5 Jul 2017  ·  21Comments  ·  Source: dvajs/dva

Hello guys,
Last two days I searched for a way how to call effectB, effectC from ModelB, ModelC inside effectA in modelA and wait for them. I read issues (#134, #325, and many more) but without any luck to find solution.

After user login I want to init few models before show dashboard.

appModel

* modelsInit({}, { put }) {
   yield put({ type: 'loadingShow' });
   yield put({ type: 'activity/query' });
   yield put({ type: 'xyz.../query' });
   yield put({ type: 'loadingHide' });
},

activityModel

  namespace: 'activity',
  .
  .
  .
  effects: {
    * query({ payload }, { call, put }) {
      const response = yield call(activityService.query, parse(payload));
      ...
  },
  .
  .
  .

Because "put" is asynchronous, 'loadingHide' is unfortunately called before 'activity/query' is finished.

Is there any elegant solution to this problem?

Thank you for your help in advance @sorrycc, @helloyou2012, @cycgit, @nikogu, @LeuisKen, @nihgwu, @zuiidea, @longzhaobi or anyone else ;o).

Most helpful comment

@vipcxj 你的这种方式会造成大量的重复逻辑 我是这种方式实现的
```javascript
{
namespace: 'app1',
effects: {
* run(param, {select, put, call, take}) {
yield put({type: 'loadingShow'});
yield put({type: 'app2/run'});
yield take('app2/runOk');
yield put({type: 'app3/run'});
yield take('app3/runOk');
yield put({type: 'loadingHide'});
}
},
};
````

```javascript
{
namespace: 'app2',
effects: {
* run(param, {select, put, call}) {
yield call(...);
yield put({type: 'runOk'});
}
},
};
````

javascript { namespace: 'app3', effects: { * run(param, {select, put, call}) { yield call(...); yield put({type: 'runOk'}); } }, }; ```` 但都避免不了造成阻塞 @sorrycc 是否有什么非阻塞的实现方式,我感觉可以从take入手 take 要是支持 `且` 的逻辑关系就可以实现非阻塞了 类似这种 javascript
{
namespace: 'app1',
effects: {
* run(param, {select, put, call, take}) {
yield put({type: 'loadingShow'});
yield put({type: 'app2/run'});
yield put({type: 'app3/run'});
yield take('app2/runOk') && yield take('app3/runOk')
yield put({type: 'loadingHide'});
}
},
};
````

All 21 comments

call in effect accept a function which return promise. effect is a function generater. do not use it as argument of call. Just put your async code in a function which return promist, and use it as the argument of call in effect.

Thank you for answer @vipcxj.
I tried to do that before but have problem with yield or put.

Can you provide example in my context?

use put to dispatch reducer, use call to call a function which return a promise.

  • modelsInit({ payload }, { put, call }) {
    yield put({ type: 'loadingShow' });
    yield call(activityService.query, parse(payload));
    yield put({ type: 'xyz.../query' });
    yield put({ type: 'loadingHide' });
    },

I don't want to call activity service in appModel if I have the service already defined in activityModel and all the next logic.
Also I don't want to use callback, especially if I want to use around 10+ x "yield put({ type: 'xyz.../query' });"

I need to call

yield put({ type: 'activity/query' });
yield put({ type: 'xyz.../query' });
yield put({ type: 'xyz2.../query' });
.
.
.

and then (after all of them) set the loading.

yield put({ type: 'loadingHide' });

(something like async/await)

Because .../query will fill data into store, maybe I can check all the needed states in my component and if its filled, dispatch 'loadingHide' action. That's maybe a way to go but still I am interested to example how to wait for more than one _put_ without callback.

It is impossible, yield is not magic, it rely on co framework. In co framework, yield accept a thunk fuction or promise.

Interesting.
Thank you for your patience and time!

I am wrong. yield accept generator, and effect is a generator. so use call and give call a function generator not a string. just like this:
javascript const effect= function * effect({ payload }, { put, call, select }) { yield put ... yield call ... yield select ... ... } const model = { effects: { effect, anotherEffect: *(action, { put, call, select }) { const payloadForEffect = ... yield call(effect, { payload: payloadForEffect }, { put, call, select }); } } }

@vipcxj it's not helpful, use another effect function or not doesn't matter the result
in my case, i want to get a taskList and save it to log model, then in the other model get the first taskId as a url param


log model:

    * getLogTaskList({ payload: { buildId } }, { call, update }) {
      console.log('2. before inner fetch');
      const { tasks: taskList } = yield call(LogServices.getLogTaskList, buildId);
      console.log('3. before inner update');
      yield update({ taskList });
      console.log('4. after inner update');
    },

othermodel:

    * jumpToLogTask({ payload: { buildId } }, {put, select }) {

        console.log('1. outer fetch');
        yield put('log/getLogTaskList', { buildId });
        console.log('5. outer select taskList');
        const taskList = yield select(state => state.log.taskList);

      // or use effect function
      // const effect = function*() {
      //   console.log('1. outer fetch');
      //   yield put('log/getLogTaskList', { buildId });
      //   console.log('5. outer select taskList');
      //   const taskList = yield select(state => state.log.taskList);
      // };
      // yield call(effect);

      if (taskList.length) {
        yield put(routerRedux.push(`/log/${taskList[0].id}`));
      }
    },

the result is always 1 5 2 3 4, maybe the only solution is callback
will this still be a problem in the next major version? @sorrycc

@daskyrk use call, not put. call for async function, put for action.

@vipcxj I truly used call, i defined the function effect and yield call(effect) in the comment, i means it's useless as without call the effect function。is there anything i misunderstand?

       const effect = function*() {
         console.log('1. outer fetch');
         yield put('log/getLogTaskList', { buildId }); //don't use put, use call, and don't use string as argument, use function generator.
         console.log('5. outer select taskList');
         const taskList = yield select(state => state.log.taskList);
       };

@vipcxj 你的这种方式会造成大量的重复逻辑 我是这种方式实现的
```javascript
{
namespace: 'app1',
effects: {
* run(param, {select, put, call, take}) {
yield put({type: 'loadingShow'});
yield put({type: 'app2/run'});
yield take('app2/runOk');
yield put({type: 'app3/run'});
yield take('app3/runOk');
yield put({type: 'loadingHide'});
}
},
};
````

```javascript
{
namespace: 'app2',
effects: {
* run(param, {select, put, call}) {
yield call(...);
yield put({type: 'runOk'});
}
},
};
````

javascript { namespace: 'app3', effects: { * run(param, {select, put, call}) { yield call(...); yield put({type: 'runOk'}); } }, }; ```` 但都避免不了造成阻塞 @sorrycc 是否有什么非阻塞的实现方式,我感觉可以从take入手 take 要是支持 `且` 的逻辑关系就可以实现非阻塞了 类似这种 javascript
{
namespace: 'app1',
effects: {
* run(param, {select, put, call, take}) {
yield put({type: 'loadingShow'});
yield put({type: 'app2/run'});
yield put({type: 'app3/run'});
yield take('app2/runOk') && yield take('app3/runOk')
yield put({type: 'loadingHide'});
}
},
};
````

@vipcxj Eh... Is there anyway I can call another model's effect without import it ?

@jindada 赞,dva@2 里也是类似这种方式。

@daskyrk 反正都是中国人,就不说鸟语了~ 方法很多吧,只不过都要自己封装,并且前提是能够得到顶级model,解析effect的字符串,在顶级model里找到那个effect,然后想怎么搞就怎么搞喽~ 不过我不清楚如果我create了一个app,dva是否能保证这个app就是我create的那个app,而不会什么时候又拷贝了一个,或者dva已经有提供在model里拿到其他model的方法了?

@vipcxj 现在貌似还没有。。不过倒从来没用过take方法, @jindada 你的意思是用put异步式的调用其他model里的effect,然后用take同步式的阻塞等待参数里的action执行完,不知我理解的对不对?
而这一句:支持 且 的逻辑关系就可以实现非阻塞了 指的是'app2/run'和'app3/run'这两个异步effect可以同时执行,然后用take同步等待结果?

嗯嗯 异步的 想要的是当 'app2/run' 和 'app3/run' 都执行完的时候 再往下走 saga 的 take 只支持的逻辑 的逻辑一起期待dva@2吧

soga,还有take这种操作,学习了

@JonasJonny
Put.resolve() is blocked
for example:
yield put.resolve({ type: 'user/fetchCurrent', IdDangVien, });

@mikelhpdatke Thank you for contribution.
The question was connected with DVA v1.x.x.
I am not sure but I think put.resolve is "enabled" from v2.x.x.

Anyway, put.resolve is definitely the solution in v2.

const response = yield put.resolve({
  type: 'nameOfEffect',
  payload: {...},
});

return response;
Was this page helpful?
0 / 5 - 0 ratings