zoukankan      html  css  js  c++  java
  • redux-saga学习进阶篇一

    今日学习目录

    一、错误处理try/catch

    二、 take/takeEvery 监听未来的action

    三、无阻塞调用 fork、canceled

    四、同时执行多个任务

    一、错误处理try/catch

    我们可以使用熟悉的 try/catch 语法在 Saga 中捕获错误。

    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 })
      }
    }
    
    

    为了测试故障案例,我们将使用 Generator 的 throw 方法。

    // 在这个案例中,我们传递一个模拟的 error 对象给 throw,
    // 这会引发 Generator 中断当前的执行流并执行捕获区块(catch block)。
    import { call, put } from 'redux-saga/effects'
    import Api from '...'
    
    const iterator = fetchProducts()
    
    // 期望一个 call 指令
    assert.deepEqual(
      iterator.next().value,
      call(Api.fetch, '/products'),
      "fetchProducts should yield an Effect call(Api.fetch, './products')"
    )
    
    // 创建一个模拟的 error 对象
    const error = {}
    
    // 期望一个 dispatch 指令
    assert.deepEqual(
      iterator.throw(error).value,
      put({ type: 'PRODUCTS_REQUEST_FAILED', error }),
      "fetchProducts should yield an Effect put({ type: 'PRODUCTS_REQUEST_FAILED', error })"
    )
    
    

    当然了,你并不一定得在 try/catch 区块中处理错误,你也可以让你的 API 服务返回一个正常的含有错误标识的值。例如, 你可以捕捉 Promise 的拒绝操作,并将它们映射到一个错误字段对象。

    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
    
    

    二、 take/takeEvery 监听未来的action

    // 1. 使用 takeEvery('*')(使用通配符 * 模式),我们就能捕获发起的所有类型的 action。
    import { select, takeEvery } from 'redux-saga/effects'
    
    function* watchAndLog() {
      yield takeEvery('*', function* logger(action) {
        const state = yield select()
    
        console.log('action', action)
        console.log('state after', state)
      })
    }
    
    
    // 2. 使用 take Effect 来实现和上面相同的功能:
    
    import { select, take } from 'redux-saga/effects'
    
    function* watchAndLog() {
      while (true) {
        const action = yield take('*')
        const state = yield select()
    
        console.log('action', action)
        console.log('state after', state)
      }
    }
    
    

    take 就像我们更早之前看到的 call 和 put。它创建另一个命令对象,告诉 middleware 等待一个特定的 action。 正如在 call Effect 的情况中,middleware 会暂停 Generator,直到返回的 Promise 被 resolve。 在 take 的情况中,它将会暂停 Generator 直到一个匹配的 action 被发起了。 在以上的例子中,watchAndLog 处于暂停状态,直到任意的一个 action 被发起。

    使用 take 组织代码有一个小问题。在 takeEvery 的情况中,被调用的任务无法控制何时被调用, 它们将在每次 action 被匹配时一遍又一遍地被调用。并且它们也无法控制何时停止监听。

    // 主动拉取 action 的另一个好处是我们可以使用熟悉的同步风格来描述我们的控制流。
    
    function* loginFlow() {
      while (true) {
        yield take('LOGIN')
        // ... perform the login logic
        yield take('LOGOUT')
        // ... perform the logout logic
      }
    }
    
    

    take Effect 让我们可以在一个集中的地方更好地去描述一个非常规的流程。

    三、无阻塞调用 fork、canceled

    在上面这个例子中,使用已拥有的effects实现上述

    // 下面代码存在一个小问题
    import { take, call, put } 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})
        return token
      } catch(error) {
        yield put({type: 'LOGIN_ERROR', error})
      }
    }
    
    function* loginFlow() {
      while(true) {
        const {user, password} = yield take('LOGIN_REQUEST')
        const token = yield call(authorize, user, password)
        if(token) {
          yield call(Api.storeItem({token}))
          yield take('LOGOUT')
          yield call(Api.clearItem('token'))
        }
      }
    }
    
    

    存在的一个问题是:

    当 loginFlow 在 authorize 中被阻塞了,最终发生在开始调用和收到响应之间的 LOGOUT 将会被错过, 因为那时 loginFlow 还没有执行 yield take('LOGOUT')。

    所以为了让 loginFlow 不错过一个并发的 LOGOUT,我们不应该使用 call 调用 authorize 任务,而应该使用 fork。并且,自从 authorize 的 action 在后台启动之后,我们获取不到 token 的结果(因为我们不应该等待它)。 所以我们需要将 token 存储操作移到 authorize 任务内部。

    import { fork, call, take, put } 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})
      } catch(error) {
        yield put({type: 'LOGIN_ERROR', error})
      }
    }
    
    function* loginFlow() {
      while(true) {
        const {user, password} = yield take('LOGIN_REQUEST')
        yield fork(authorize, user, password)
        yield take(['LOGOUT', 'LOGIN_ERROR'])
        yield call(Api.clearItem('token'))
      }
    }
    
    

    但是还没完。

    如果我们在 API 调用期间收到一个 LOGOUT action,我们必须要 取消 authorize 处理进程,否则将有 2 个并发的任务, 并且 authorize 任务将会继续运行,并在成功的响应(或失败的响应)返回后发起一个 LOGIN_SUCCESS action(或一个 LOGIN_ERROR action),而这将导致状态不一致。

    // 为了取消 fork 任务,我们可以使用一个指定的 Effect cancel。
    import { take, put, call, fork, cancel } from 'redux-saga/effects'
    
    // ...
    
    function* loginFlow() {
      while(true) {
        const {user, password} = yield take('LOGIN_REQUEST')
        // fork return a Task object
        const task = yield fork(authorize, user, password)
        const action = yield take(['LOGOUT', 'LOGIN_ERROR'])
        if(action.type === 'LOGOUT')
          yield cancel(task)
        yield call(Api.clearItem('token'))
      }
    }
    
    

    假设在我们收到一个 LOGIN_REQUEST action 时,我们在 reducer 中设置了一些 isLoginPending 标识为 true,以便可以在界面上显示一些消息或者旋转 loading。 如果此时我们在 Api 调用期间收到一个 LOGOUTaction,并通过 杀死它(即任务被立即停止)简单粗暴地中止任务。 那我们可能又以不一致的状态结束了。因为 isLoginPending 仍然是 true,而 reducer 还在等待一个结果 action(LOGIN_SUCCESS 或 LOGIN_ERROR)。

    // 幸运的是,cancel Effect 不会粗暴地结束我们的 authorize 任务,
    // 相反它会给予一个机会执行清理的逻辑。
    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
        }
      }
    }
    
    

    四、同时执行多个任务

    // 错误写法,effects 将按照顺序执行
    const users = yield call(fetch, '/users'),
          repos = yield call(fetch, '/repos')
    
    
    // 由于第二个 effect 将会在第一个 call 执行完毕才开始。所以我们需要这样写:
    import { call } from 'redux-saga/effects'
    
    // 正确写法, effects 将会同步执行
    const [users, repos] = yield [
      call(fetch, '/users'),
      call(fetch, '/repos')
    ]
    
    
  • 相关阅读:
    wepack性能优化-contenthash(缓存)
    webpack优化系列-oneOf
    webpack性能优化-source-map
    webpack性能优化-HMR
    vue__之路由懒加载
    vue__之ref的作用
    圆面积和球体积
    算法思想
    看完com本质论第一章
    windows消息机制
  • 原文地址:https://www.cnblogs.com/fe-linjin/p/11134680.html
Copyright © 2011-2022 走看看