Subscribers keep adding after each file save instead of clearing

Using OS X, latest version of Fuse and OS X, preview on Local (not tested elsewhere).

When you add a subscriber to an Observable that was declared in a different file (and required from the current file), each edit of the subscriber function will be added to the list of subscribers of the Observable, instead of clearing the list between saves (which is what happens when everything is declared in the same file).

Cf. full minimal example: https://fuseweb.azureedge.net/forum-user-uploads/2016/08/22/SFWPopvKgaPR-legacy-files-Qqsu80DSycC29uLV-Nr6OM9DNl7qyVMhqSVIi23lE.zip

I try to have a data store managed independently (redux-like) while takig advantage of the Observable class, so this bug really messes with that approach.

Hi!

If you are manually adding subscriptions to an observable, you must also manually remove those subscriptions when it is no longer needed. The global module that you required is not reset when you change a different file.

We might be missing some features here to support what you are doing, but I don’t fully understand what you are trying to do.

A robust way of doing this is to never create subscriptions explicitly, but simply use reactive operators (e.g. map()) all the way to module.exports. This way the UX will manage subscriptions correctly for you.

If there is no way around. Here is a small trick to get an event where you can unsubscribe when the current javascript tag is unrooted.

UXHelperModule.uno

using Uno;
using Uno.UX;
using Fuse.Scripting;

namespace ResetModuleTest
{
    [UXGlobalModule]
    public class UXHelperModule : NativeModule
    {
        static UXHelperModule _instance;
        static NativeEvent _onReify;

        public UXHelperModule()
        {
            if(_instance != null) return;
            Resource.SetGlobalKey(_instance = this, "UXHelper");

            AddMember(_onReify = new NativeEvent("onreify"));
            Reset += OnReset;
        }

        void OnReset(object sender, EventArgs args)
        {
            if (_onReify!=null)
                _onReify.RaiseAsync();
        }
    }
}

and in js:

var uxhelper = require('UXHelper');
uxhelper.onreify = function() {
    console.log('hello');
};

Thanks Anders & Anders. What I’m trying to do is to have a separate state store as a module, that can be required by any other module so that each module is aware of the state of the application.

Thus, for one module to be able to react to a change that was triggered by another part of the code, the addSubscriber function seemed to be perfect, but apparently will not work. What I’m not sure I understand is “The global module that you required is not reset when you change a different file.” If that’s the case, how using .map() will produce a different behaviour?

Indeed I did some tests with .map() and the results are the same. Basically, if I declare an Observable in module-a.js and add subcribers to it (either with addSubscriber() or map()) in file-b.js, every time I will save file-b.js, the subscribers will accumulate on the Observable. The only way to clear the subscribers list is to do a file save on file-a.js as well.

Anders B. maybe your module is supposed to solve that but I don’t really understand how to use it :confused: looks like the onreify event is never fired for me.

Cyril: That’s not correct. The map function does not add a subscriber. Are you sure you are not adding subscribers in addition? Can you provide a complete examples that illustrates?

Sure, bear with me:

MainView.ux

<App>
  <JavaScript File="timer.js" />
  <Panel>
    <Text Alignment="Center" Value="{pub}"></Text>
  </Panel>
</App>

timer.js

var Observable = require('FuseJS/Observable');
var Store = require('./store');

var log = function (i, it) { console.log('map updated', i, it); return i; };

var pub = Store.pub.map(log);

Store.pub.value = 'no';

module.exports = {
    pub: pub
};

store.js

var Observable = require('FuseJS/Observable');

var pub = Observable('start');

setTimeout(function () {
    pub.value = 'start again';
}, 2000);

module.exports = {
    pub: pub
};

So you have 3 files, one that is exposed to UX (timer.js) and one that is require by another. Considering the way the code is setup, I would expect the value displayed on screen to always be set to what is in store.js after 2000ms, due to the setTimeout().

However, depending on which file you save last, the value will either change after 2000ms (when you save store.js) or keep the value set in timer.js (when you save timer.js). Say you change the line Store.pub.value = 'no'; to Store.pub.value = 'yes';, the preview will display yes even after 2000ms.

Considering the way the code is setup, I would expect the value displayed on screen to always be set to what is in store.js after 2000ms, due to the setTimeout().

No - only the file you change, and files that depend on it, will be re-evaluated.

What you are describing is the expected behavior.

If you want all files to re-evaluate, do a Cmd/Ctrl+R in the preview window.

OK I believe my confusion was “files that depend on it”. Because I set the value of a variable that is declared in another file, I assumed they were dependent on each other and should both be re-evaluated.

Although you confirm that the opposite will work? If I save a file, all files that require it will be re-evaluated?

If I save a file, all files that require it will be re-evaluated?

Yes, that should always happen.

Recently merged to master is a feature called module.disposed that can be used to clean up explicit subscriptions:

Changelog:

  • Added module.disposed feature. Use module.disposed = function() { ... to clean up resources, observable subscriptions etc. held by a <JavaScript> object. The function will be called when the <JavaScript> object is unrooted/removed from the app.
  • Cleaned up object lifetime bugs related to multiple <JavaScript> tags in the same ux:Class.

Expected to go out in Fuse 0.23