内容简介:笔者最近在做一些后台项目,使用的是在redux-saga中,saga是指一些长时操作,用generator函数表示。generator函数的强大之处在于其可以手动的暂停、恢复执行,且可以与函数体外进行数据交互,看如下例子:可以看出来genrator函数何时进行下一步操作完全取决于外部的调度时机,且其内部执行状态也由外部的输入决定,这使得generator函数可以很方便的做异步流程控制。举个例子,我们首先读取一个文件的内容作为查询参数,然后请求一个查询接口并把返回的内容打印出来:
笔者最近在做一些后台项目,使用的是 Ant Design Pro ,其使用了 redux-saga 处理异步数据流,本文将对redux-saga的原理做一个简单的解读,并将实现一个 简易版的redux-saga 。
Generator函数的自动流程控制
在redux-saga中,saga是指一些长时操作,用generator函数表示。generator函数的强大之处在于其可以手动的暂停、恢复执行,且可以与函数体外进行数据交互,看如下例子:
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函数可以很方便的做异步流程控制。举个例子,我们首先读取一个文件的内容作为查询参数,然后请求一个查询接口并把返回的内容打印出来:
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函数的执行:
const g = gen(); g.next().value.then(params => { g.next(params).value.then(content => { g.next(content); }) }) 复制代码
以上可以达到我们的目的,但是过于繁琐,我们想要的是generator函数可以自动的执行,可以写一个简易的自动执行函数如下:
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的类型执行不同的处理逻辑。
Effect
事实上 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,它可能类似如下:
{ isEffect: true, type: 'CALL', fn: genPromise } 复制代码
事实上effect只表明了意图,而实际的行为由类似于上文的nextWithYieldType完成,例如:
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,希望对你有所帮助。
全文完。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Dojo权威指南
拉塞尔 / 李松峰、李丽 / 机械工业出版社 / 2009-4 / 79.00元
通过使用Dojo这个工业强度的JavaScript工具箱,我们可以比使用其他任何Ajax框架更高效、更容易地创建JavaScript或Ajax驱动的应用程序和站点。 《Dojo权威指南》向读者展示了如何充分利用Dojo工具箱中包含的大量实用特性,以前所未有的效率开发出功能丰富、响应敏捷的Web应用程序。读者通过《Dojo权威指南》能够学习到创建复杂布局和表单控件(常见于高级桌面应用程序)的技......一起来看看 《Dojo权威指南》 这本书的介绍吧!