Concurrency

As soon as something in javascript becomes asynchronous, things can get really complicated. The reason for this is that we always have to deal with concurrency. Generally we have to deal with two types of concurrency:

  • Concurrency between rules: You can define the execution order by giving the rule a weight. See the weight section for this
  • Concurrency between consequences: A rule's consequence can be executed before the last execution has finished.

Fortunately redux-ruleset has a special key for you that can handle (nearly) all types of concurrency in a very easy way:

addRule({
  id: 'FETCH_CART',
  target: 'cart/FETCH_REQUEST',
  concurrency: 'FIRST', // as long as the the concurrency is running, no new concurrency will start
  consequence: () => api.fetchCart().then(
    cart => actions.fetchCartSuccess(cart),
    error => actions.fetchCartFailure(error)
  )
})

The concurrency defines what should happen when a new consequence should be invoked while the old one hasn't finished yet. You can choose beween the following options:

  • DEFAULT: this is the default behaviour; consequences are executed in a uncontrolled way. If your consequence is async it should always implement a concurrency behaviour (there are no implications to performance)
  • FIRST: As long as there is a running concurrency all future consequences won't be called
  • LAST: As soon as a concurrency is called, all running concurrencies are instantly canceled
  • ONCE: Same as FIRST, but even when the first concurrency is canceled no other concurrency will get called ever
  • SWITCH: All concurrencies are normally called. As soon as a later called concurrency dispatches (or send a event) all previous concurrencies are canceled

DEFAULT

As long as your consequences are totally synchonous you do not have to set a concurrency logic. Then the DEFAULT logic will be applied that calls the consequences just as you would expect

FIRST

addRule({
  id: 'FETCH_PRODUCT',
  target: 'products/FETCH_REQUEST',
  concurrency: 'FIRST',
  connsequence: () => api.fetchProduct().then(
    result => actions.fetchProductSuccess(result),
    error => actions.fetchProductFailure(error)
  )
})

dispatch({type: 'products/FETCH_REQUEST'}) // consequence gets invoked
dispatch({type: 'products/FETCH_REQUEST'}) // consequence gets not invoked
dispatch({type: 'products/FETCH_REQUEST'}) // consequence gets not invoked

await next('products/FETCH_SUCCESS')

dispatch({type: 'products/FETCH_REQUEST'}) // consequence gets invoked

As long as there is a running concurrency all future consequences won't be called. That can also be usefull when it comes to streams to keep only the first socket open

LAST

addRule({
  id: 'FETCH_PRODUCT',
  target: 'products/FETCH_REQUEST',
  concurrency: 'LAST',
  connsequence: () => api.fetchProduct().then(
    result => actions.fetchProductSuccess(result),
    error => actions.fetchProductFailure(error)
  )
})

dispatch({type: 'products/FETCH_REQUEST'}) // consequence gets invoked
dispatch({type: 'products/FETCH_REQUEST'}) // consequence gets invoked and prev consequence gets cannceled
dispatch({type: 'products/FETCH_REQUEST'}) // consequence gets invoked and prev consequence gets cannceled

await next('products/FETCH_SUCCESS')

dispatch({type: 'products/FETCH_REQUEST'}) // consequence gets invoked

As soon as a concurrency is called, all running concurrencies are instantly canceled. That can also be usefull when it comes to streams to keep only the most recent one

ONCE

addRule({
  id: 'FETCH_PRODUCT',
  target: 'products/FETCH_REQUEST',
  concurrency: 'ONCE',
  connsequence: () => api.fetchProduct().then(
    result => actions.fetchProductSuccess(result),
    error => actions.fetchProductFailure(error)
  )
})

dispatch({type: 'products/FETCH_REQUEST'}) // consequence gets invoked
dispatch({type: 'products/FETCH_REQUEST'}) // consequence gets not invoked
dispatch({type: 'products/FETCH_REQUEST'}) // consequence gets not invoked

await next('products/FETCH_SUCCESS')

dispatch({type: 'products/FETCH_REQUEST'}) // consequence gets not invoked

The rule will execute the consequence only once during the lifetime of the rule. When you recreate the consequence can be executed again.

SWITCH

addRule({
  id: 'FETCH_PRODUCT',
  target: 'products/FETCH_REQUEST',
  concurrency: 'SWITCH',
  connsequence: () => api.fetchProduct().then(
    result => actions.fetchProductSuccess(result),
    error => actions.fetchProductFailure(error)
  )
})

dispatch({type: 'products/FETCH_REQUEST'}) // E1: consequence gets invoked
dispatch({type: 'products/FETCH_REQUEST'}) // E2: consequence gets invoked
dispatch({type: 'products/FETCH_REQUEST'}) // E3: consequence gets invoked
dispatch({type: 'products/FETCH_REQUEST'}) // E4: consequence gets invoked

// fetchProduct resolves with the following order: E1 -> E3 -> E2 -> E4
// only E1, E3 and E4 map to fetchProductSuccess

The rule executes the consequence everytime the target matches. However it is canceled (no dispatch gets invoked) when a later called consequence dispatches or resolves (or sends an effect). This is super usefull when you fetch data and only want to keep the last response (e.g you set a filter within a product-list). With the concurrency SWITCH you can be shure that a slow response won't override a faster one

results matching ""

    No results matching ""