原文地址git
笔者最近在作一些后台项目,使用的是Ant Design Pro,其使用了redux-saga处理异步数据流,本文将对redux-saga的原理作一个简单的解读,并将实现一个简易版的redux-saga。github
在redux-saga中,saga是指一些长时操做,用generator函数表示。generator函数的强大之处在于其能够手动的暂停、恢复执行,且能够与函数体外进行数据交互,看以下例子:json
function *gen() {
const a = yield 'hello';
console.log(a);
}
cont g = gen();
g.next(); // { value: 'hello', done: false }
setTimeout(() => g.next('hi'), 1000) // 此时 a => 'hi' 一秒后打印‘hi'
复制代码
能够看出来genrator函数什么时候进行下一步操做彻底取决于外部的调度时机,且其内部执行状态也由外部的输入决定,这使得generator函数能够很方便的作异步流程控制。举个例子,咱们首先读取一个文件的内容做为查询参数,而后请求一个查询接口并把返回的内容打印出来:redux
function getParams(file) {
return new Promise(resolve => {
fs.readFile(file, (err, data) => {
resolve(data)
})
})
}
function getContent(params) {
// request返回promise
return request(params)
}
function *gen() {
const params = yield getParams('config.json');
const content = yield getContent(params);
console.log(content);
}
复制代码
咱们能够手动控制gen函数的执行:promise
const g = gen();
g.next().value.then(params => {
g.next(params).value.then(content => {
g.next(content);
})
})
复制代码
以上能够达到咱们的目的,可是过于繁琐,咱们想要的是generator函数能够自动的执行,能够写一个简易的自动执行函数以下:bash
function genRun(gen) {
const g = gen();
next();
function next(err, pre) {
let temp;
(err === null) && (temp = g.next(pre));
(err !== null) && (temp = g.throw(pre));
if(!temp.done) {
nextWithYieldType(temp.value, next);
}
}
}
function nextWithYieldType(value, next) {
if(isPromise(value)) {
value
.then(success => next(null, success))
.catch(error => next(error))
}
}
genRun(gen);
复制代码
此时generator函数即可以自动执行,事实上咱们能够发现,generator的内部状态彻底是由nextWithYieldType
决定的,咱们能够根据yield的类型执行不一样的处理逻辑。异步
事实上sagaMiddleware.run(saga)
能够相似看作genRun(saga)
,而saga是由一个个的effect组成的,那么effect是什么?redux-saga官网的解释:一个 effect 就是一个 Plain Object JavaScript 对象,包含一些将被 saga middleware 执行的指令。redux-saga提供了不少Effect建立器,如call
、put
、take
等,已call
为例:函数
function saga*() {
const result = yield call(genPromise);
console.log(result);
}
复制代码
call(genPromise)
生成的就是一个effect,它可能相似以下:ui
{
isEffect: true,
type: 'CALL',
fn: genPromise
}
复制代码
事实上effect只代表了意图,而实际的行为由相似于上文的nextWithYieldType完成,例如:spa
function nextWithYieldType(value, next) {
...
if(isCallEffect(value)) {
value.fn(). then(success => next(null, success)).catch(error => next(error))
}
}
复制代码
当genPromise函数返回的promise被resolve后便会打印出结果。
观察下面的例子
function *saga() {
yield take('TEST');
console.log('test...');
}
sagaMiddleware.run(test);
复制代码
saga会在take('TEST')
处阻塞,只有执行了dispatch({type: 'TEST'})
后saga才能继续运行(注意:此时的dispatch
方法是通过sagaMiddleware包装过的)。这给咱们的感受彷佛很像是take
是一个生产者,在等待disaptch
的消费,事实上take
只是一个Effect生成器,具体的处理逻辑依然是在nextWithYieldType完成的,相似于:
function nextWithYieldType(value, next) {
...
// take('TEST')生成的effect简单的认为是 {isEffect: true, type: 'TAKE', name: 'TEST'}
if(isTakeEffect(value)) {
channel.take({pattern: value.name, cb: params => next(null, params)})
}
}
复制代码
channel是一个任务生成器,它有两个方法:take生成任务,put消费任务:
function channel() {
/* task = { pattern, cb } */
let _task = null;
function take(task) {
_task = task;
}
function put(pattern, args) {
if(!_task) return;
if(pattern == _task.pattern) _task.cb.call(null, args);
}
return {
take,
put
}
}
复制代码
显然任务是在执行dispatch
的时候被消费掉的,这个工做是在sagaMiddleware中作的,相似于以下:
const sagaMiddleware = store => {
return next => action => {
next(action);
const { type, ...payload } = action;
channel.put(type, payload);
}
}
复制代码
看到这里咱们能够发现,须要咱们作的就是不断的完善nextWithYieldType这个函数,当完成了put
、fork
、takeEvery
对应的逻辑后,一个具有基本功能的redux-saga就诞生啦,笔者就不在赘述这些功能的实现了。最后,你能够查看这里:tiny-redux-saga,这是笔者实现的一个简易版的redux-saga,但愿对你有所帮助。
全文完。