Stimulus Discourse

Multiple function call on button click synchronously with async-await not working

Hi Guys,

I have posted this question on StackOverflow, cross posting it here too.

I am trying to figure out how to call multiple methods with 1 button click synchronously. My first function is an AJAX call. So it’s naturally not synchronous. To solve that issue I have used async-await. Async-await is waiting inside the same function, but not on the other functions that are being called.

I am using Stimulus.js to create classes, but general answer is enough for me: https://stimulusjs.org/

This is what my code looks like:

Button: onClick:

.button{data: { action:"classA#methodA classB#methodB" } }

in class A

async methodA(event){
  const response = await axios.post('/url', { data }, options);
  const data = await response.json();
  doSomething()
}

in class B

methodB(){
  updateThings()
}

So what I want to achieve is classA#methodA classB#methodB be called synchronously. But methodA is not waiting for methodB even though methodA is not compete. And even when I am using async await on methodA.

Any idea on how I can solve this issue?

Creating a wrapper method and calling them doesn’t seem possible because of the way Stimulus.js works.

Hi @sandipsubedi! I had a similar problem. I worked around it by having my updateThings set a hidden field when complete and then setup a timeout loop on methodA to check for that to be set:

function methodA() {
    console.log("checking if methodB is ready")
    if (methodBReady()) {
        loopRunning = false;
        methodAStuff();
    } else {
        setTimeout(function () {
            methodA()
        }, 500)
    }
}

Hey @sandipsubedi!

Because you have two classes here that are trying to coordinate, I’d recommend firing off an event, which will allow you to keep things loosely coupled (so Class A doesn’t need to know about Class B and vice-versa).

In Class A

async methodA(event){
  const response = await axios.post('/url', { data }, options);
  const data = await response.json();
  doSomething()
  
  // Use detail to include additional info in your event
  const  evt = new CustomEvent('somethingUpdated', {detail: {results:data}})
  this.element.dispatchEvent(eve)
}

Then your button looks like:

.button{data: { action:"classA#methodA" } }

And your instance of Class B becomes something like:

<div data-controller="classB" data-action="window@somethingUpdated->classB#methodB">...</div>

Now Class B will be notified immediately when Class A is complete. Note that custom events happen synchronously, so methodB will be triggered immediately and in the same call stack. This can be good or bad (generally speaking it’s okay) - if you wanted to wait a tick before calling methodB (let’s say it’s a slow function) to keep the UI responsive, you could use Window.requestAnimationFrame to dispatch your event from.

CustomEvent can take an optional detail property that allows you to pass additional data along with your event

Depending on the nature of the work being done and the event being dispatched, you may want to dispatch the event from doSomething instead of from methodA - then regardless of how/when doSomething is called, any interested parties will be notified.

Also, you can have as many controllers as you’d like listening for the somethingUpdated event.

Hope this helps! If you have any trouble, let me know!

2 Likes