- Blocking
阻塞调用的意思是: saga 会在 yield 了 effect 后会等待其执行结果返回,结果返回后才恢复执行 generator 中的下一个指令 - Non-blocking
非阻塞调用的意思是: saga 会在 yield effect 之后立即恢复执行
| Name | Blocking |
|---|---|
| takeEvery | No |
| takeLatest | No |
| takeLeading | No |
| throttle | No |
| debounce | No |
| retry | Yes |
| take | Yes |
| take(channel) | Sometimes (see API reference) |
| takeMaybe | Yes |
| put | No |
| putResolve | Yes |
| put(channel, action) | No |
| call | Yes |
| apply | Yes |
| cps | Yes |
| fork | No |
| spawn | No |
| join | Yes |
| cancel | No |
| select | No |
| actionChannel | No |
| flush | Yes |
| cancelled | Yes |
| race | Yes |
| delay | Yes |
| all | Blocks if there is a blocking effect in the array or object |
注意:阻塞和非阻塞 不代表 异步与同步 我们将通过下面的例子理解阻塞的意思
Call vs Fork
- call 会阻塞当前 saga 的执行,直到被调用函数 fn 返回结果,才会执行下一步代码。
- fork 则不会阻塞当前 saga,会立即返回一个 task 对象。
需要注意,这里的函数 fn 可以是一个 generator函数 ,还可以是一个 普通函数 。
普通函数
- 当 fn 为一个普通函数时,这时候
fork和call并无明显不同
// 模拟数据异步获取
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函数 时,
fork和call表现出阻塞和非阻塞
// 模拟数据异步获取
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 所谓的非阻塞是什么呢?
- put 是发起一个 action,这个是同步操作。而这个 action 实际是一个 reducer(reducer本身就应该是“同步的”),所以会同步更新store,后面拿到的 store 信息肯定是更新后的。
- 非阻塞意思是,假如这个 action 中有中间件,或一些异步操作造成了 store 信息更新不及时,那么 effects 中并不会等着这些操作执行完,即会继续执行接下来的操作。
Put vs 副作用
如果action里有异步操作,那么结果会如何呢?
store.js与reducer.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一起用才行