Redux-Saga中的阻塞与非阻塞

1,145 阅读3分钟
  • Blocking
    阻塞调用的意思是: saga 会在 yield 了 effect 后会等待其执行结果返回,结果返回后才恢复执行 generator 中的下一个指令
  • Non-blocking
    非阻塞调用的意思是: saga 会在 yield effect 之后立即恢复执行
NameBlocking
takeEveryNo
takeLatestNo
takeLeadingNo
throttleNo
debounceNo
retryYes
takeYes
take(channel)Sometimes (see API reference)
takeMaybeYes
putNo
putResolveYes
put(channel, action)No
callYes
applyYes
cpsYes
forkNo
spawnNo
joinYes
cancelNo
selectNo
actionChannelNo
flushYes
cancelledYes
raceYes
delayYes
allBlocks if there is a blocking effect in the array or object

注意:阻塞和非阻塞 不代表 异步与同步 我们将通过下面的例子理解阻塞的意思

Call vs Fork

  • call 会阻塞当前 saga 的执行,直到被调用函数 fn 返回结果,才会执行下一步代码。
  • fork 则不会阻塞当前 saga,会立即返回一个 task 对象。 需要注意,这里的函数 fn 可以是一个 generator函数 ,还可以是一个 普通函数

普通函数

  • 当 fn 为一个普通函数时,这时候forkcall并无明显不同
// 模拟数据异步获取
function fn() {
  console.log("hello world")
}

function* fetchData() {
  const greeting = yield call(fn);
  console.log('call: ', greeting);

  const greeting = yield fork(fn);
  console.log('fork: ', greeting);
}
//最终打印
call:hello world
fork:hello world

generator函数

  • 当 fn 为 generator函数 时,forkcall表现出阻塞和非阻塞
// 模拟数据异步获取
function fn() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('hello saga');
    }, 2000);
  });
}

function* fetchData() {
  // 等待 2 秒后,打印欢迎语(阻塞)
  const greeting = yield call(fn);
  console.log('hello: ', greeting);

  // 立即打印 task 对象(非阻塞)
  const task = yield fork(fn);
  console.log('task: ', task);
}

倘若在fork情况下,你依然要获取返回结果,可以这样做:

const task = yield fork(fn);
// 0.16.0 api
task.done().then((greeting) => {
  console.log('hello: ', greeting);
});

//  1.0.0-beta.0 api
task.toPromise().then((greeting) => {
  console.log('hello: ', greeting);
});

Put vs PutResolve

  • put 会非阻塞的发起一个 action
  • putResolve 会阻塞的发起一个 action

阻塞与异步

假设我们有一个 store ,其初始 user 为 null,有一个saga,能够发起 fetchUser API获取到用户信息
伪代码如下
store.js

{
  user: null
}

reducer.js

setUser((state, payload) => {
    console.log('setUser Success: ', payload);
    const newState = {...state, user: payload};    
    return newState;
})

saga.js

function* getUser() {
    const user = (yield call(fetchUser)).data;  // 假设返回 { name: CC, age:17}
    yield put(setUser(user));
    console.log('fetchUser Success: ', yield select(state => state.user));
}

如果阻塞 === 异步,那这里打印很有可能是

fetchUser Success: null
setUser Success: { name: CC, age:17 }

实际打印却是:

setUser Success: { name: CC, age:17 }
fetchUser Success: { name: CC, age:17 }

这也就说明,yield put 一个 action,reducer 执行完毕且更新到 store 后才继续执行接下来的操作。那 put 所谓的非阻塞是什么呢?

  1. put 是发起一个 action,这个是同步操作。而这个 action 实际是一个 reducer(reducer本身就应该是“同步的”),所以会同步更新store,后面拿到的 store 信息肯定是更新后的。
  2. 非阻塞意思是,假如这个 action 中有中间件,或一些异步操作造成了 store 信息更新不及时,那么 effects 中并不会等着这些操作执行完,即会继续执行接下来的操作。

Put vs 副作用

如果action里有异步操作,那么结果会如何呢?
store.jsreducer.js不变

saga.js

function* fetchUser() {
    const user = (yield call(getUserById,userId)).data;  // 假设返回 { name: CC, age:17}    
    yield put(setUser(user)); 
}


function* getUser() {
    const userId = (yield call(fetchId)).data;   // 修改点1
    yield put(fetchUser(userId)); // 修改点2
    console.log('fetchUser Success: ', yield select(state => state.user));
}

结果:

fetchUser Success: null
setUser Success: { name: CC, age:17 }

此时就能发现,yield put(fetchUser(userId));这句是非阻塞的,它并没有等待fetchUser执行结束就往下走了

PutResolve vs 副作用

store.js

import thunkMiddleware from "redux-thunk";
import createSagaMiddleware from "redux-saga";
import React from "react";
import ReactDOM from "react-dom";
import { createStore, applyMiddleware } from "redux";
import reducer from "./reducers";
import rootSaga from "./sagas";

const sagaMiddleware = createSagaMiddleware();

const store = createStore(reducer,applyMiddleware(thunkMiddleware, sagaMiddleware));

sagaMiddleware.run(rootSaga);

thunks.js

export const fetchUser = (userId) => (dispatch) => {
    getUserById(userId)
    .then((res) => {
        dispatch(setUser(res));
    })
};

saga.js

function* getUser() {
    const userId = (yield call(fetchId)).data; 
    yield putResolve(fetchUser(userId)); // 修改点
    console.log('fetchUser Success: ', yield select(state => state.user));
}

结果:

setUser Success: { name: CC, age:17 }
fetchUser Success: { name: CC, age:17 }

据笔者实验及管网Demo,putResolve目前是和thunk一起用才行