question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

RFC: Reset derived state

See original GitHub issue

Summary

In this issue, I would like to propose public API to reset the derived state for a task. Calling the function would cause the last* task object properties to reset to undefined, allowing interface that depends on these properties to “start over”.

Motivation

The motivation for this change is best described with an example. I know I’ve looked for the existence of this functionality in the documentation for other use-cases. I have chosen this use case as my example as it is what I am working on right now and is therefore freshest.

I have a form, where at first there is one dropdown element, and when an item from the first dropdown is selected, an ajax request is fired to load options for a second element. In my current case, there is also a third dropdown, whose options get loaded when an item is selected from the second dropdown.

The following will use country, region, and city, as an arbitrary and hopefully understandable example. Below are a component template and implementation to illustrate how I accomplish this now. Derived state is reset by writing the task to return undefined, thereby unsetting the value from lastSuccessful.

Template
{{ember-power-select options=countries onChange=(action 'selectCountry')}}

{{! Not shown for simplicity: a loading indicator when a task isRunning }}

{{#if regions}}
  {{ember-power-select options=regions onChange=(action 'selectRegion')}}
{{/if}}

{{#if cities}}
  {{ember-power-select options=cities onChange=(action (mut 'city'))}}
{{/if}}
Component
import Component from '@ember/component';
import { readOnly } from '@ember/object/computed';
import { task } from 'ember-concurrency';

export default Component.extend({
  countries: [...],

  regions: readOnly('loadRegions.lastSuccessful.value'),
  cities: readOnly('loadCities.lastSuccessful.value'),

  loadRegions: task(function*(country) {
    return yield ajax(...);
  }),

  loadCities: task(function*(region) {
    // When called with an explicitly falsy value, return undefined to reset
    // derived state.
    if (!region) { return; }

    return yield ajax(...);
  }),

  actions: {
    selectCountry(country) {
      this.set('country', country);

      // If a region and city have been selected, and the country is changed,
      // we need to reset the state of the cities, or the cities dropdown
      // will still be populated with options from the previously selected
      // country. This explicitly resets the derived state for cities.
      this.get('loadCities').cancelAll();
      this.get('loadCities').perform(null);

      this.get('loadRegions').perform(country);
    },

    selectRegion(region) {
      this.set('region', region);
      this.get('loadCities').perform(region);
    },
  },
});

This approach works, but doesn’t feel idomatic and requires awkward special case code in the task body itself.

Detailed design

I would hope it would be possible to expose something like a reset() function on the task property, so that, given the previous example, it would be possible to call this.get('loadCities').reset() instead of this.get('loadCities').perform(null).

Component re-implementation
import Component from '@ember/component';
import { readOnly } from '@ember/object/computed';
import { task } from 'ember-concurrency';

export default Component.extend({
  countries: [...],

  regions: readOnly('loadRegions.lastSuccessful.value'),
  cities: readOnly('loadCities.lastSuccessful.value'),

  loadRegions: task(function*(country) {
    return yield ajax(...);
  }),

  loadCities: task(function*(region) {
    return yield ajax(...);
  }),

  actions: {
    selectCountry(country) {
      this.set('country', country);

      // If a region and city have been selected, and the country is changed,
      // we need to reset the state of the cities, or the cities dropdown
      // will still be populated with options from the previously selected
      // country. This explicitly resets the derived state for cities.
      this.get('loadCities').reset();

      this.get('loadRegions').perform(country);
    },

    selectRegion(region) {
      this.set('region', region);
      this.get('loadCities').perform(region);
    },
  },
});

I don’t know enough of the ember-concurrency internals to know how best to implement this. Would it be possible to schedule an empty task. This could either be explicitly scheduling null or a null-object TaskInstance?

What happens if there are currently running tasks?

I see two options for how reset() would work, and I’m not sure which would be better, although I would learn toward the first option here as it represents my use-case the best.

  1. It might make sense for resetting to cancelAll and reset derived state

    In this case, it might make more sense to reset the entire scheduler, although I’m not sure what other implications that might have.

  2. Another option, and my assumption is that this makes the most sense, is that reseting is equivalent to scheduling a new empty task

    This means the new empty task would follow the normal concurrency rules. I think this might make sense for enqueue, restartable, and keepLatest, but might not make sense with drop.

How we teach this

The new function could be documented in the API documentation, and I think it would make sense to add a reset button to the examples in the Derived State documentation.

Drawbacks

One drawback is adding to the API surface area. Another drawback is potential confusion about how it works, especially depending on what approach is taken about how to handle currently running tasks.

Alternatives

The obvious alternative is to not add this functionality, as there is a workaround.

Unresolved questions

My biggest question is already outlined above in “What happens if there are currently running tasks?”.

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:2
  • Comments:9 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
amielcommented, Oct 19, 2018

UPDATE: Check out #253, which works for me in manual testing on my application. If you mentioned this would be useful, would you give it a shot by referencing my branch in package.json and trying it out?

1reaction
machtycommented, Sep 26, 2018

Also I think we can safely change the first string arg to cancelAll into a hash of options:

  • reason: the reason for the cancel (mostly used internally for helpful logging)
  • resetState: (default: false) whether to restart the state

I snuck in the reason string arg as private API and it hasn’t been documented; I highly doubt the change would break anyone’s code.

Read more comments on GitHub >

github_iconTop Results From Across the Web

RFC 5961: Improving TCP's Robustness to Blind In-Window ...
A reset is valid if its sequence number is in the window. In the SYN-SENT state (a RST received in response to an...
Read more >
RFC 3588 Diameter Based Protocol - IETF
This document also defines the Diameter failover algorithm and state machine. ... The transport connection MUST be closed using a RESET call (send...
Read more >
Transmission Control Protocol - Wikipedia
TCP employs network congestion avoidance. However, there are vulnerabilities in TCP, including denial of service, connection hijacking, TCP veto, and reset ...
Read more >
RFC 7540 - Hypertext Transfer Protocol Version 2 (HTTP/2)
If the TCP connection is closed or reset while streams remain in "open" or "half-closed" state, then the affected streams cannot be automatically...
Read more >
Rfc2898DeriveBytes Class (System.Security.Cryptography)
RFC 2898 includes methods for creating a key and initialization vector (IV) ... Creates a PBKDF2 derived key from a password. Reset(). Resets...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found