Redux-saga学习笔记javascript
概述java
Redux-saga在Redux应用中扮演’中间件’的角色,主要用来执行数据流中的异步操做。主要经过ES6中的generator函数和yield关键字来以同步的方式实现异步操做。redux
基本用法:api
APIpromise
用来监听action,每一个action都触发一次,若是其对应是异步操做的话,每次都发起异步请求,而不论上次的请求是否返回。缓存
import { takeEvery } from 'redux-saga/effects' function* watchFetchData() { yield takeEvery('FETCH_REQUESTED', fetchData) }
做用同takeEvery同样,惟一的区别是它只关注最后,也就是最近一次发起的异步请求,若是上次请求还未返回,则会被取消。app
function* watchFetchData() { yield takeLatest('FETCH_REQUESTED', fetchData) }
call用来调用异步函数,将异步函数和函数参数做为call函数的参数传入,返回一个js对象。saga引入他的主要做用是方便测试,同时也能让咱们的代码更加规范化。异步
同js原生的call同样,call函数也能够指定this对象,只要把this对象当第一个参数传入call方法就行了函数
saga一样提供apply函数,做用同call同样,参数形式同js原生apply方法。 单元测试
import { call } from 'redux-saga/effects' function* fetchProducts() { const products = yield call(Api.fetch, '/products') // ... } import { call } from 'redux-saga/effects' import Api from '...' const iterator = fetchProducts() // expects a call instruction assert.deepEqual( iterator.next().value, call(Api.fetch, '/products'), "fetchProducts should yield an Effect call(Api.fetch, './products')" ) yield call([obj, obj.method], arg1, arg2, ...) yield apply(obj, obj.method, [arg1, arg2, ...])
同call方法基本同样,可是用处不太同样,call通常用来完成异步操做,cps能够用来完成耗时比较长的io操做等。
put是saga对Redux中dispatch方法的一个封装,调用put方法后,saga内部会分发action通知Store更新state。
这个借口主要也是为了方便咱们写单元测试提供的。
import { call, put } from 'redux-saga/effects' // ... function* fetchProducts() { const products = yield call(Api.fetch, '/products') // create and yield a dispatch Effect yield put({ type: 'PRODUCTS_RECEIVED', products }) } const products = {} // expects a dispatch instruction assert.deepEqual( iterator.next(products).value, put({ type: 'PRODUCTS_RECEIVED', products }), "fetchProducts should yield an Effect put({ type: 'PRODUCTS_RECEIVED', products })" )
有两种方式来处理请求失败的状况,一种是使用try-catch方法,将错误抛出;另外一种是使用变量缓存成功失败的状态。
import Api from './path/to/api' import { call, put } from 'redux-saga/effects' // ... function* fetchProducts() { try { const products = yield call(Api.fetch, '/products') yield put({ type: 'PRODUCTS_RECEIVED', products }) } catch(error) { yield put({ type: 'PRODUCTS_REQUEST_FAILED', error }) } } import Api from './path/to/api' import { call, put } from 'redux-saga/effects' function fetchProductsApi() { return Api.fetch('/products') .then(response => ({ response })) .catch(error => ({ error })) } function* fetchProducts() { const { response, error } = yield call(fetchProductsApi) if (response) yield put({ type: 'PRODUCTS_RECEIVED', products: response }) else yield put({ type: 'PRODUCTS_REQUEST_FAILED', error }) }
take的表现同takeEvery同样,都是监听某个action,但与takeEvery不一样的是,他不是每次action触发的时候都相应,而只是在执行顺序执行到take语句时才会相应action。
当在genetator中使用take语句等待action时,generator被阻塞,等待action被分发,而后继续往下执行。
takeEvery只是监听每一个action,而后执行处理函数。对于什么时候相应action和 如何相应action,takeEvery并无控制权。
而take则不同,咱们能够在generator函数中决定什么时候相应一个action,以及一个action被触发后作什么操做。
最大区别:take只有在执行流达到时才会响应对应的action,而takeEvery则一经注册,都会响应action。
import { take, put } from 'redux-saga/effects' function* watchFirstThreeTodosCreation() { for (let i = 0; i < 3; i++) { const action = yield take('TODO_CREATED') } yield put({type: 'SHOW_CONGRATULATION'}) }
非阻塞任务调用机制:上面咱们介绍过call能够用来发起异步操做,可是相对于generator函数来讲,call操做是阻塞的,只有等promise回来后才能继续执行,而fork是非阻塞的 ,当调用fork启动一个任务时,该任务在后台继续执行,从而使得咱们的执行流能继续往下执行而没必要必定要等待返回。
import { take, call, put, cancelled } from 'redux-saga/effects' import Api from '...' function* authorize(user, password) { try { const token = yield call(Api.authorize, user, password) yield put({type: 'LOGIN_SUCCESS', token}) yield call(Api.storeItem, {token}) return token } catch(error) { yield put({type: 'LOGIN_ERROR', error}) } finally { if (yield cancelled()) { // ... put special cancellation handling code here } } }
cancel的做用是用来取消一个还未返回的fork任务。防止fork的任务等待时间太长或者其余逻辑错误。
all提供了一种并行执行异步请求的方式。以前介绍过执行异步请求的api中,大都是阻塞执行,只有当一个call操做放回后,才能执行下一个call操做, call提供了一种相似Promise中的all操做,能够将多个异步操做做为参数参入all函数中,若是有一个call操做失败或者全部call操做都成功返回,则本次all操做执行完毕。
import { all, call } from 'redux-saga/effects' // correct, effects will get executed in parallel const [users, repos] = yield all([ call(fetch, '/users'), call(fetch, '/repos') ])
有时候当咱们并行的发起多个异步操做时,咱们并不必定须要等待全部操做完成,而只须要有一个操做完成就能够继续执行流。这就是race借口的用处。他能够并行的启动多个异步请求,只要有一个 请求返回(resolved或者reject),race操做接受正常返回的请求,而且将剩余的请求取消。
import { race, take, put } from 'redux-saga/effects' function* backgroundTask() { while (true) { ... } } function* watchStartBackgroundTask() { while (true) { yield take('START_BACKGROUND_TASK') yield race({ task: call(backgroundTask), cancel: take('CANCEL_TASK') }) } }
在以前的操做中,全部的action分发是顺序的,可是对action的响应是由异步任务来完成,也便是说对action的处理是无序的。
若是须要对action的有序处理的话,可使用actionChannel函数来建立一个action的缓存队列,但一个action的任务流程处理完成后,才但是执行下一个任务流。
代码参考:
import { take, actionChannel, call, ... } from 'redux-saga/effects' function* watchRequests() { // 1- Create a channel for request actions const requestChan = yield actionChannel('REQUEST') while (true) { // 2- take from the channel const {payload} = yield take(requestChan) // 3- Note that we're using a blocking call yield call(handleRequest, payload) } } function* handleRequest(payload) { ... }
用来防止接二连三的响应某个事件。
import { throttle } from 'redux-saga/effects' function* handleInput(input) { // ... } function* watchInput() { yield throttle(500, 'INPUT_CHANGED', handleInput) }
延时执行,使用delay函数实现
import { delay } from 'redux-saga' function* handleInput(input) { // debounce by 500ms yield call(delay, 500) ... } function* watchInput() { let task while (true) { const { input } = yield take('INPUT_CHANGED') if (task) { yield cancel(task) } task = yield fork(handleInput, input) } } const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms))
参考:https://redux-saga.js.org/docs/api/