内容简介:第一次看到这个单词的时候一脸懵逼,因为字典上查到的意思完全驴头不对马嘴。。。实际上,这个术语出自康奈尔大学的一篇论文:最初这篇论文是为了解决分布式系统中的LLT(Long Lived Transaction),也就是长时运行事务的数据一致性问题的。这么说有点抽象,我们来举个具体的例子:
第一次看到这个单词的时候一脸懵逼,因为字典上查到的意思完全驴头不对马嘴。。。
实际上,这个术语出自康奈尔大学的一篇论文: www.cs.cornell.edu/andru/cs711…
最初这篇论文是为了解决分布式系统中的LLT(Long Lived Transaction),也就是长时运行事务的数据一致性问题的。这么说有点抽象,我们来举个具体的例子:
假如你在一个在线订票系统上订了一张机票,下单成功后,一般会给你留30分钟付款时间,如果你在30分钟内完成付款就可以成功出票,否则会被取消预定。也就是说,从下单到出票,最长可能需要30分钟,这就是传说中的LLT。用过数据库的同学肯定都知道,所谓“事务(Transaction)”,指的是一个原子操作,要么全部执行,要么全部回滚。那么问题来了,为了保证数据的一致性,我们是不是应该等待刚才那个LLT执行完成呢?这显然不现实,因为这意味着在这30分钟内,其他人都没办法订票了。。。于是,在1987年,康奈尔大学的两位大佬发表了一篇论文,提出了一个新的概念,叫做saga:
Let us use the term saga to refer to a LLT that can be broken up into a collection of sub-transactions that can be interleaved in any way with other transactions
具体是什么意思呢?还是以上面的订票系统为例,两位大佬说了,我们可以把这个LLT拆成两个子事务嘛,T1表示“预定”事务,T2表示“出票”事务。先执行T1,然后就可以把数据库释放出来了,其他人也可以正常订票了。如果用户在30分钟内完成了付款,那么再执行T2完成出票,这样整个事务就执行完毕了。假如超过了30分钟用户还没有付款怎么办?这时候需要执行一个“补偿”事务C1,用来回滚T1对数据库造成的修改。这几个子事务组合在一起,就叫一个saga:
当然,上面的例子只是最简单的情况,实际应用中的LLT可能非常复杂,包含非常多的子事务:
另外还有更复杂的并行saga,这里就不介绍了。看到这里,你可能会觉得,这好像也没啥嘛,本来就应该这么做啊。是的,如果你早出生30年,没准发论文的就就是你了^_^
2.副作用(Side Effect)
还需要再介绍一个概念:副作用(Side Effect)。
如果有一天我跟你说你提交的代码有side effect,其实我是在委婉地说,你的代码搞出bug来了。。。当然,这跟我们这里讨论的side effect不是一回事儿。我们这里讨论的side effect出自于“函数式编程”,这种编程范式鼓励我们多使用“纯函数”。所谓纯函数,指的是一个函数满足以下两个特点:
- 输出不受外部环境影响:同样的输入一定可以获得同样的输出
- 不影响外部环境:包括但不限于修改外部数据、发起网络请求、触发事件等等。。。
为什么要多用纯函数呢?因为它们具有很强的“可预测性”。既然有纯函数,那肯定有不纯的函数喽,或者换个说法,叫做有“副作用”的函数。我们可以看一下维基百科上的定义:
In computer science, an operation, function or expression is said to have a side effect if it modifies some state variable value(s) outside its local environment, that is to say has an observable effect besides returning a value (the main effect) to the invoker of the operation.
显然,大多数的异步任务都需要和外部世界进行交互,不管是发起网络请求、访问本地文件或是数据库等等,因此,它们都会产生“副作用”。
3.什么是redux-saga?
redux-saga是一个Redux中间件,用来帮你管理程序的副作用 。或者更直接一点,主要是用来处理异步action。
上一篇我们介绍过Redux的中间件,说白了就是在action被传递到reducer之前新进行了一次拦截,然后启动异步任务,等异步任务执行完成后再发送一个新的action,调用reducer修改状态数据。redux-saga的功能也是一样的,参见下图:
左边的蓝圈圈里就是一堆saga,它们需要和外部进行异步I/O交互,等交互完成后再修改Store中的状态数据。redux-saga就是一个帮你管理这堆saga的管家,那么它跟其他的中间件实现有什么不同呢? 它使用了ES6中Generator函数语法 。
4.ES6的Generator函数
Javascript的语法一直在演进,其中最为重要的因素之一就是为了简化异步调用的书写方式。
从最初的callback“回调地狱”:
step1(value0, function(value1) { step2(value1, function(value2) { step3(value2, function(value3) { console.log(value3) }) }) }) 复制代码
到后来的Promise链式调用:
step1(value0) .then(value1 => step2(value1)) .then(value2 => step3(value2)) .then(value3 => console.log(value3)) .catch(error => console.log(error)) 复制代码
再到ES6中引入的Generator函数:
function* mySaga(value0) { try { var value1 = yield step1(value0) var value2 = yield step2(value1) var value3 = yield step3(value2) console.log(value3) } catch(e) { console.log(e) } } 复制代码
可以看到,Generator函数的写法基本上和同步调用完全一样了,唯一的区别是function后面有个星号,另外函数调用之前需要加上一个yield关键字。
看起来似乎很完美,但是实际上没有这么简单。下面这张图描述了Generator函数的实际调用流程:
当你调用mySaga()时,其实并没有真正执行函数,而只是返回了一个迭代器(Iterator)。你必须要通过迭代器的next()函数才能执行第1个yield后面的step1()函数:
var it = mySaga(value0) it.next() 复制代码
另外,当step1()执行完异步任务后,需要再次调用it.next()才能继续执行下一个yield后面的异步函数。所以step1()可能会类似下面这个样子,step2()/step3()也是一样:
const step1 = (value0) => { makeAjaxCall(value0) .then(response => it.next(response)) } 复制代码
不过,幸运的是,redux-saga已经帮我们封装好了这一切,你只要专心实现异步调用逻辑就可以了。
5.redux-saga用法
根据上一节的分析,我们不仅需要实现一个Generator函数,还需要提供一个外部驱动函数。这在redux-saga中被称为 worker saga 和 watcher saga :
- worker saga:具体业务逻辑实现
- watcher saga:接收特定的action,然后驱动worker saga执行
我们来看一个具体的例子:
import Api from '...' function* workerSaga(action) { try { const user = yield call(Api.fetchUser, action.payload.userId); yield put({type: "USER_FETCH_SUCCEEDED", user: user}); } catch (e) { yield put({type: "USER_FETCH_FAILED", message: e.message}); } } function* watcherSaga() { yield takeEvery("USER_FETCH_REQUESTED", workerSaga); } 复制代码
我们先看一下watcherSaga:watcherSaga中使用了redux-saga提供的API函数takeEvery(),当有接收到USER_FETCH_REQUESTED action时,会启动worker saga。另一个常用的辅助函数时takeLatest(),当有相同的action发送过来时,会取消当前正在执行的任务并重新启动一个新的worker saga。
然后我们看下workerSaga,可以看到并不是直接调用异步函数或者派发action,而是通过call()以及put()这样的函数。这就是redux-saga中最为重要的一个概念: Effect 。
实际上,我们可以直接通过yield fetchUser()执行我们的异步任务,并返回一个Promise。但是这样的话不好做模拟(mock)测试:我们在测试过程中,一般不会真的执行异步任务,而是替换成一个假函数。实际上,我们只需要确保yield了一个正确的函数,并且函数有着正确的参数。
因此,相比于直接调用异步函数, 我们可以仅仅 yield 一条描述函数调用的指令 ,由redux-saga中间件负责解释执行该指令,并在获得结果响应时恢复Generator的执行。这条指令是一个纯Javascript对象(类似于action):
{ CALL: { fn: Api.fetchUser, args: ['alice'] } } 复制代码
这样,当我们需要测试Generator函数时,就可以用一条简单的assert语句来比较两个Effect对象(即使不在Redux环境中):
assert.deepEqual( iterator.next().value, call(Api.fetchUser, 'alice'), "Should yield an Effect call(Api.fetchUser, 'alice')" ) 复制代码
为了实现这一目标,redux-saga提供了一系列API函数来生成Effect对象,比较常用的是下面这几个:
- call:函数调用
- select:获取Store中的数据
- put:向Store发送action
- take:在Store上等待指定action
- fork:和call类似,但是是非阻塞的,立即返回
比如我们之前用到的takeEvery()函数,其实内部实现就是不停地take -> fork -> take -> fork …循环。当接收到指定action时,会启动一个worker saga,并驱动其中的yield调用。
借用网上的一张神图来更直观地理解上面这些API的作用:
另外,如果你想要同时监听不同的action,可以使用all()或者race()把他们组合成一个 root saga :
export default function* rootSaga() { yield all([ takeEvery("FOO_ACTION", fooASaga), takeEvery("BAR_ACTION", barASaga) ]) } 复制代码
最后,你需要在createStore()时注册redux-saga中间件,然后调用run()函数启动你的root saga就大功告成了:
import { createStore, applyMiddleware } from 'redux' import createSagaMiddleware from 'redux-saga' import reducer from './reducers' import rootSaga from './sagas' // create the saga middleware const sagaMiddleware = createSagaMiddleware() // mount it on the Store const store = createStore( reducer, applyMiddleware(sagaMiddleware) ) // then run the saga sagaMiddleware.run(rootSaga) 复制代码
今天就介绍到这里,以一张思维导图结束本篇文章:
以上所述就是小编给大家介绍的《前端技术 | redux-saga,化异步为同步》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 前端异步解决方案-4.2(generator+promise)
- 前端技术栈(三):redux-saga,化异步为同步
- 前端日拱一卒D11——ES6笔记之异步篇
- [前端怪谈_2]从 Dva 的 Effect 到 Generator + Promise 实现异步编程
- [前端漫谈_2] 从 Dva 的 Effect 到 Generator + Promise 实现异步编程
- SpringBoot | :异步开发之异步调用
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Spring Cloud微服务实战
翟永超 / 电子工业出版社 / 2017-5 / 89
《Spring Cloud微服务实战》从时下流行的微服务架构概念出发,详细介绍了Spring Cloud针对微服务架构中几大核心要素的解决方案和基础组件。对于各个组件的介绍,《Spring Cloud微服务实战》主要以示例与源码结合的方式来帮助读者更好地理解这些组件的使用方法以及运行原理。同时,在介绍的过程中,还包含了作者在实践中所遇到的一些问题和解决思路,可供读者在实践中作为参考。 《Sp......一起来看看 《Spring Cloud微服务实战》 这本书的介绍吧!
JS 压缩/解压工具
在线压缩/解压 JS 代码
SHA 加密
SHA 加密工具