New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

effector-reeffect

Package Overview
Dependencies
Maintainers
1
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

effector-reeffect

Concurrent effects for Effector

  • 0.1.1
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
70
increased by66.67%
Maintainers
1
Weekly downloads
 
Created
Source

effector-reeffect

Build Status Coverage Status License NPM Made with Love

ReEffects for Effector ☄️
Like regular Effects, but better :)

  • Supports different launch strategies: TAKE_FIRST, TAKE_LAST, TAKE_EVERY
  • Handles promises cancellation
  • Can handle logic cancellation

Table of Contents

Install

$ yarn add effector-reeffect

Or using npm

$ npm install --save effector-reeffect

Usage

In basic version you can use it like regular Effect:

import { createReEffect } from 'effector-reeffect'

// create ReEffect
const fetchUser = createReEffect({
  handler: ({ id }) =>
    fetch(`https://example.com/users/${id}`).then(res => res.json()),
})

Nothing special yet, created ReEffect has same properties, as usual Effect: Events done, fail, finally; Store pending; and methods use(handler), watch(watcher), prepend(fn). Check out documentation to learn more.

Magic begins when you call ReEffect more that once, while previous asynchronous operation is not finished yet 🧙‍♂️

import { createReEffect, TAKE_LAST } from 'effector-reeffect'

// create ReEffect
const fetchUser = createReEffect({
  handler: ({ id }) =>
    fetch(`https://example.com/users/${id}`).then(res => res.json()),
})

// call it once
fetchUser({ id: 1 }, TAKE_LAST)

// and somewhere in a galaxy far, far away
fetchUser({ id: 1 }, TAKE_LAST)

You see the new TAKE_LAST argument - this is called strategy, and TAKE_LAST one ensures, that only latest call will trigger done (or fail) event. Each call still remain separate Promise, so you can await it, but first one in the example above will be rejected with CancelledError instance (you can import this class from package and check error instanceof CancelledError).

Strategies

TAKE_EVERY

This is default strategy, if you will not specify any other.

TAKE_EVERY

Second effect call will launch second asynchronous operation. In contrast with usual Effect, ReEffect will trigger .done (or .fail) event only for latest operation, and .pending store will contain true for a whole time of all operations (in other words, if there is at least single pending operation - .pending will hold true).

TAKE_FIRST

TAKE_FIRST

Second effect call will be immediately rejected with CancelledError (handler will not be executed at all).

TAKE_LAST

TAKE_LAST

Second effect call will reject all currently pending operations (if any) with CancelledError.

Properties

ReEffect has few new properties:

  • .cancelled: Event triggered when any handler is rejected with CancelledError or LimitExceededError (this will be described later).
  • .cancel: Event you can trigger to manually cancel all currently pending operations - each cancelled operation will trigger .cancelled event.

Options

createReEffect function accepts same arguments as usual Effect, with few possible additions in config:

  • strategy: this strategy will be considered as default, instead of TAKE_EVERY. Possible values: TAKE_EVERY, TAKE_FIRST, TAKE_LAST. Note, that this values are Symbols, you must import them from package!
  • limit: maximum count of simultaneously running operation, by default Infinity. If new effect call will exceed this value, call will be immediately rejected with LimitExceededError error.
const fetchUser = createReEffect('fetchUser', {
  handler: ({ id }) =>
    fetch(`https://example.com/users/${id}`).then(res => res.json()),
  strategy: TAKE_LAST,
  limit: 3,
})

ReEffect, created with createReEffect function, behave like usual Effect, with one difference: in addition to effect's payload you can specify strategy as a second argument (or first, if effect doesn't have payload). This strategy will override default strategy for this effect (but will not replace default strategy).

fetchUser({ id: 2 }, TAKE_EVERY)

Cancellation

ReEffect will handle Promises cancellation for you (so handler promise result will be ignored), but it cannot cancel logic by itself! There are quite an amount of possible asynchronous operations, and each one could be cancelled differently (and some could not be cancelled at all).

But bright side of it is that you can tell ReEffect, how to cancel your logic ☀️

To do this, handler accepts onCancel callback as a second argument, and you can specify, what actually to do on cancel.

Let me show an example:

import { createReEffect, TAKE_LAST } from 'effector-reeffect'

const reeffect = createReEffect({ strategy: TAKE_LAST })

reeffect.watch(_ => console.log('reeffect called', _))
reeffect.done.watch(_ => console.log('reeffect done', _))
reeffect.fail.watch(_ => console.log('reeffect fail', _))
reeffect.cancelled.watch(_ => console.log('reeffect cancelled', _))

reeffect.use(
  params =>
    new Promise(resolve => {
      setTimeout(() => {
        console.log(`-> AHA! TIMEOUT FROM EFFECT WITH PARAMS: ${params}`)
        resolve('done')
      }, 1000)
    })
)

reeffect(1)
reeffect(2)

If you will run code above, you will get

reeffect called { params: 1, strategy: Symbol(TAKE_LAST) }
reeffect called { params: 2, strategy: Symbol(TAKE_LAST) }
reeffect cancelled { params: 1,
  strategy: Symbol(TAKE_LAST),
  error:
   Error: Cancelled due to "TAKE_LAST" strategy, new effect was added }
-> AHA! TIMEOUT FROM EFFECT WITH PARAMS: 1
-> AHA! TIMEOUT FROM EFFECT WITH PARAMS: 2
reeffect done { params: 2, strategy: Symbol(TAKE_LAST), result: 'done' }

As you can see, first effect call was rejected and cancelled, but timeout itself was not cancelled, and printed message.

Let's change code above:

import { createReEffect, TAKE_LAST } from 'effector-reeffect'

const reeffect = createReEffect({ strategy: TAKE_LAST })

reeffect.watch(_ => console.log('reeffect called', _))
reeffect.done.watch(_ => console.log('reeffect done', _))
reeffect.fail.watch(_ => console.log('reeffect fail', _))
reeffect.cancelled.watch(_ => console.log('reeffect cancelled', _))

reeffect.use((params, onCancel) => {
  let timeout
  onCancel(() => clearTimeout(timeout))
  return new Promise(resolve => {
    timeout = setTimeout(() => {
      console.log(`-> AHA! TIMEOUT FROM EFFECT WITH PARAMS: ${params}`)
      resolve('done')
    }, 1000)
  })
})

reeffect(1)
reeffect(2)

Now ReEffect know, how to cancel your Promise's logic, and will do it while cancelling operation:

reeffect called { params: 1, strategy: Symbol(TAKE_LAST) }
reeffect called { params: 2, strategy: Symbol(TAKE_LAST) }
reeffect cancelled { params: 1,
  strategy: Symbol(TAKE_LAST),
  error:
   Error: Cancelled due to "TAKE_LAST" strategy, new effect was added }
-> AHA! TIMEOUT FROM EFFECT WITH PARAMS: 2
reeffect done { params: 2, strategy: Symbol(TAKE_LAST), result: 'done' }

This could be done with any asynchronous operation, which supports cancellation or abortion.

axios

Axios supports cancellation via cancel token:

reeffect.use(({ id }, onCancel) => {
  const source = CancelToken.source()
  onCancel(() => source.cancel())
  return axios.get(`https://example.com/users/${id}`, {
    cancelToken: source.token,
  })
})

fetch

Fetch API supports cancellation via AbortController (read more):

reeffect.use(({ id }, onCancel) => {
  const controller = new AbortController()
  onCancel(() => controller.abort())
  return fetch(`https://example.com/users/${id}`, {
    signal: controller.signal,
  })
})

ky

Ky is built on top of Fetch API, and supports cancellation via AbortController as well:

reeffect.use(({ id }, onCancel) => {
  const controller = new AbortController()
  onCancel(() => controller.abort())
  return ky(`https://example.com/users/${id}`, {
    signal: controller.signal,
  }).json()
})

request

Request HTTP client supports .abort() method:

reeffect.use(({ id }, onCancel) => {
  let r
  onCancel(() => r.abort())
  return new Promise((resolve, reject) => {
    r = request(`https://example.com/users/${id}`, (err, resp, body) =>
      err ? reject(err) : resolve(body)
    )
  })
})

XMLHttpRequest

If you happen to use good old XMLHttpRequest, I will not blame you (but others definitely will). Good to know it supports cancellation too, via .abort() method:

reeffect.use(({ id }, onCancel) => {
  let xhr
  onCancel(() => xhr.abort())
  return new Promise(function(resolve, reject) {
    xhr = new XMLHttpRequest()
    xhr.open('GET', `https://example.com/users/${id}`)
    xhr.onload = function() {
      if (this.status >= 200 && this.status < 300) {
        resolve(xhr.response)
      } else {
        reject({
          status: this.status,
          statusText: xhr.statusText,
        })
      }
    }
    xhr.onerror = function() {
      reject({
        status: this.status,
        statusText: xhr.statusText,
      })
    }
    xhr.send()
  })
})

Sponsored

Setplex

Keywords

FAQs

Package last updated on 05 Dec 2019

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc