DEV Community

Vlad
Vlad

Posted on

Using a Promise in a click eventListener - more than once.

Hello everyone,
I'm trying to wrap my head around how the vanilla MVC should be done right, and existing resourses have been of great help. Now I need help with something I can't seem to find - or formulate a proper query for.
Here's the relevant part of the code that I have:

        //view
        class CollectorView{
            constructor(){
                this.element1 = document.getElementById('collections');
                this.element2 = document.getElementById('collections__photos');         
            }

            ...

            returnCollectionsId(e){
                return new Promise((resolve) => 
                    this.element1.addEventListener('click', e => 
                        (!e.target.id || e.target.id === 'collections') ? null : resolve(e.target.id)                   
                    )
                )
            } 

            async renderPhotos(viewModel){
                this.element2.innerHTML = viewModel.map(el => `<div>
                    <h3>${el.description?el.description:el.alt_description}</h3>
                    <a href="#photos__details" class="circle" id="${viewModel.indexOf(el)}"></a>
                </div>`)
                .join("");
            }
        }

        //controller
        class CollectorController{
            constructor(model,view){
                this.model = model
                this.view = view

                this.getId()
            }

            ...

            async getId(){
                let photosId = await this.view.returnCollectionsId()
                let photosPromise = this.model.sendPhotos(photosId)
                let photosArray = await photosPromise.then(rsp => rsp.data)
                this.view.renderPhotos(photosArray)
            }
        }

So what happens is: there's a click eventListener that, when a certain element is clicked, reads the id of the clicked element, sends it into the model and does an API call with it, receives the info and renders it.

While this works perfectly, there are two big problems:
1) Promise only runs once. When it resolves with an existing ID, I can't go to the previous view and click a different element - it would have already run and still renders the first thing clicked.
2) The await part of the async/await only will work as intended when it receives a Promise. So can't just pass an ID as a string/number - returns undefined and everything breaks.

So, is there an alternative that would also return a promise, but would work anew every time upon a click happening? Or something to replace async/await with?

Thank you in advance!

Top comments (7)

Collapse
 
pankajpatel profile image
Pankaj Patel

You can not reuse the promise to go back to the Pending state, but you can create a new one. You can write another generator function to get a new promise on the Fly.

And to deal with returning undefined in async/await; you can do the following:

const data = await Promise.resolve(getSomePromise(someParam));

What Promise.resolve does is that it converts a non-promise value given to it to a Promisified value and hence you can await on it; and if the value passed to Promise.resolve is already a promise, i.e. then-able value; it will continue working as a regular value

Collapse
 
alittlebyte profile image
Vlad • Edited

Yes, thank you, this is exactly what I need!
One question, though... how would I write a generator function for Promises?
Search returns nothing, besides information on the Promises themselves.
Shouldn't my existing returnCollectionsId() already work as a generator? From how I see it, it should return a new Promise everytime it is called, but it doesn't. So that's the part I don't get.
Just send an article my way that explains the topic further. Can't find one :\ Thank you again!

Collapse
 
fearless23 profile image
Jaspreet Singh

You can use Rxjs Subject and subscribe to it.

const sub = new Subject()

// Inside Event Listener Callback
sub.next(e.target.id)

// Outside or wherever you want
sub.subscribe( id => {
// add your stuff here
})

.subscribe will fire everytime button is clicked or event is fired.

Collapse
 
savagepixie profile image
SavagePixie

Why are you using promises and async/await, though? As far as I can tell you aren't doing any asynchronous operation. Have you tried just using synchronous code for everything?

Collapse
 
alittlebyte profile image
Vlad • Edited

I am, probably didn't describe it too well.
There's a model block above view that isn't included, it's responsible for API calls.

The current flow is:

  • Initial call, loading collections. (it's written the same way, but it should only execute once, so not included - works as intended)
  • Setting up an eventListener that should not activate, until an element is clicked.
  • When it's clicked, send a call with the clicked element's ID to the model. The id is that of the collection.
  • It then returns the list of photos for that collection, which is then rendered.

If I do everything synchronously, sendPhotos() model function will return an undefined error. There's only an axios call in it, and it has to wait for the ID to be defined first. Making this function async/await doesn't work, tried.

All I really wanted to know is if there's an alternative for Promises, that can be re-resolved, and searching for one hasn't given any good results. I did find a thing called Observables, but it was reserved for Angular only, and I'm making the app in vanilla JS.
Or, a way to hold await until an element is clicked and its ID is sent to it. But await only works that way with Promises, so not much to do there.

Collapse
 
copperwall profile image
Chris Opperwall

I might be wrong, but I think one way would be to pass a callback into returnCollectionsId and then call that callback from inside returnCollectionsId from the click event handler you set up. Then anytime element1 is clicked, your callback will be called, instead of just the first time.

If you wanted to use Observables I think you could use Rx.js without angular. Instead of returning a promise that sets up an on click handler and resolves when the onClick is called for the first time, you can return an Observable and subscribe to it in whatever code calls returnCollectionsId.

Here's a blog post about setting up Rx.js with click events from a few years ago codyburleson.com/respond-to-button...

Thread Thread
 
alittlebyte profile image
Vlad • Edited

Thank you, I've tried both. The way I made everything, it doesn't want to wait for anything else but a Promise, unfortunately. Still launches immediately. Or I need a couple more days for the new information to sink in, and then I'll figure out how to get it to work. Will see, thank you again!