Different behavior when triggering a watch from a jscallback vs a Panel callback

Hey there,

I’m working on building a dashboard to facilitate data processing for my lab. In my code, some file data is read with a JS callback and is then stored into a parameterized class, which then triggers a watched callback to do the processing of the data. I would like to display progressive updates in a status bar to the user, so they know things have not frozen. However, whenever I try this, it seems like the watched method is being blocked from updating the UI until everything has resolved. I’ve tried to come up with a minimal code example displaying the behavior here:

from time import sleep

import panel as pn #0.12.6
import param #1.12.0

pn.extension()

class DataStorageClass(param.Parameterized):
    value = param.Integer()
    
js_button = pn.widgets.Button(name="JS Change")
pn_button = pn.widgets.Button(name="pn Change")
status = pn.widgets.TextInput()
data_storage = DataStorageClass()

def _testfunc(*events):
    for event in events:
        if event.name == 'value':
            for i in range(10):
                status.value = str(event.new) + "\t" + str(i)
                sleep(0.2)
data_storage.param.watch(_testfunc, ['value'], onlychanged=False)

js_button.jscallback(clicks="""
    console.log("bep");
    ds.value += 1;
""",args={"ds": data_storage})
def testcallback(events):
    data_storage.value += 1
    
pn_button.on_click(testcallback)

pn.Column(
    js_button,
    pn_button,
    status,
).servable()

So when the data_storage object is updated via JS, the UI halts until the full time passes, then jumps the display to “# 9”, whereas if I update the object via the on_click callback the status bar ticks up visually as expected. Additionally, it seems like the JS based updates to the parameterized object are extremely unreliable through reloading the page. Reloading the page sometimes causes the JS button to not actually update the data_storage object, or at least not display the changes. I’m assuming this is either because there is an old instance of the data_storage object that is cached and being called instead of the new object (as I can see the canary “bep” showing up in my Chrome console, so the JS code is at least getting executed it seems), or I’m just not setting up my parameterized object correctly and am missing some sort of crucial setup code to make it play nice with Panel. I usually have to completely cancel the running “panel serve” task on my server, as the behavior crops up even when “–autoreload” is specified. The only other error I see is when I’m working in Jupyter Notebook I regularly get “[IPKernelApp] WARNING | No such comm: hv-extension-comm” pushed to the console, but from what I’ve read online this is usually harmless and have found no solutions. The JS button also straight up doesn’t work in the Jupyter Notebook environment, but does work with the served page, which is frustrating. I know bugger all about JS or the proper way to do these things, so I apologize if there is a glaringly simple flaw, but I’ve chipping away at this for a few days in my free time and am thoroughly stumped. I’m happy to provide any additional info that would help diagnose this problem.

Maybe not the most elegant/correct solution, but I’ve found a temporary solution by import asyncio and changing the reporting function to be asynchronous, and peppering the method with await asyncio.sleep(0) calls after each Panel “value” update. Happy to hear if there is a more correct solution, but I figured I’d post a working workaround. The modification to the above code to represent this is as follows:

async def _testfunc(*events):
    for event in events:
        if event.name == 'value':
            for i in range(10):
                status.value = str(event.new) + "\t" + str(i)
                await asyncio.sleep(0.2)
data_storage.param.watch(_testfunc, ['value'], onlychanged=False)