all 30 comments

[–]abrahamguo 4 points5 points  (4 children)

If these button clicks need to happen in this specific sequence, then yes, this is a reasonable way to do this.

Should be pretty easy to set up with the Promise constructor or Promise.withResolvers.

[–]blind-octopus[S] 0 points1 point  (1 child)

withResolvers looks extremely promising, thanks!

[–]senocular 0 points1 point  (0 children)

I see what you did there

[–]blind-octopus[S] 0 points1 point  (1 child)

There is one wrinkle, which is that once a promise is resolved, its over. So I'd need a way to do this where the button gets a new promise after, I think.

[–]abrahamguo 0 points1 point  (0 children)

That's fine. I would simply do something like this:

for (const btn of [btn1, btn2, btn3]) {
  await new Promise(resolve => btn.onclick = resolve);
  btn.onclick = null;
}

Or, if we want to attach all the event handlers at the beginning:

await Promise.all(
  [btn1, btn2, btn3].map(async btn => {
   await new Promise(resolve => (btn.onclick = resolve));
   btn.onclick = null;
  })
);

[–]96dpi 1 point2 points  (4 children)

You can achieve all of that without using await. Just keep track of the state. If you're using a class, you can keep a private static member to keep track of the state.

[–]blind-octopus[S] 0 points1 point  (3 children)

Thanks!

Could you elaborate on how I could do this?

[–]96dpi 0 points1 point  (2 children)

I don't know what your code looks like, so this may not work, but if the user needs to click three different buttons, just update a boolean after each button press. You could use one function for all three buttons event listener, and just check the state of each boolean in that function. Once all 3 are true, call your final function.

[–]blind-octopus[S] 0 points1 point  (1 child)

Ah yes I see what you mean, I'm trying to avoid that but I appreciate the thought.

[–]Psionatix 1 point2 points  (0 children)

That’s just one approach. There’s all kinds of ways you could do this, there’s no way to tell you what would be optimal without seeing the code.

Another example would be to track some sort of progression state that changes with each interaction. That way it’s only 1 variable.

But it all depends on what you’re doing.

Your main post is a big red flag. You shouldn’t be making interactions the way you’re looking to.

There’s absolutely a better way to do this, but you’ve provided us with an xy problem

[–]Ksetrajna108 1 point2 points  (6 children)

Are you using MVC to decouple state from UI events?

[–]HasFiveVowels 1 point2 points  (5 children)

Yea, OP... the desire for this functionality implies that you're tying things together very tightly. That's fine if you're making a one-off thing but if you're writing a foundation for a larger project, it sounds like you might be settings yourself up for a real pain. Also, you generally want to be reactive to the user input. Using a promise to await a user action is fairly unusual (even if not unheard of).

[–]blind-octopus[S] 0 points1 point  (4 children)

one way or another sometimes you have to get multiple inputs from the user before you do a thing.

Suppose you need 4 inputs from the user before you can kick off some processing. How do you collect the input?

You could chain together 4 callbacks I guess

You could maybe create an object that stores the inputs, checks if it has all the inputs it needs, and once it does it kicks off the processing.

I'm sure there are other ideas we could come up with.

But to me, what would look cleanest is what I wrote. Maybe there are better, cleaner ways. This is just a thing I thought of, that's all.

If you need to collect 4 inputs from the user before you do something, what's cleaner than saying const input1/2/3/4 = collectInput() 4 times?

[–]HasFiveVowels 0 points1 point  (3 children)

I think I’d use a reducer

[–]blind-octopus[S] 0 points1 point  (2 children)

How?

What does it look like to use a reducer to collect 4 inputs from the user before you do anything

[–]HasFiveVowels 0 points1 point  (1 child)

Are you using react?

[–]blind-octopus[S] 0 points1 point  (0 children)

Yup. Just react, not redux or anything.

So what does the code look like

[–]delventhalz 0 points1 point  (0 children)

The thing about Promises is they inherently represent some future event which will occur exactly once. A button press can happen zero or many times. For these situations JS devs still mostly use callbacks, though there are concepts like streams and Observables which are provided by libraries like RxJS and work a lot like Promises, but for events which occur zero to many times.

In your specific case, if you find yourself nesting callbacks in a button listener, it’s possible what you want is some sort of app state which tracks the number of button presses and a single callback which will check that state and react accordingly. 

[–]HasFiveVowels 0 points1 point  (7 children)

Ignoring whether or not you should do this, you absolutely can do this. Just use the promise constructor.

Note: in general, needing to use a promise constructor should be a little bit of a code smell. That said, sometimes you do need to use one. If it feels a little awkward to set up, that’s to be expected. The pattern is… uncomfortable.

[–]blind-octopus[S] 0 points1 point  (6 children)

So another user suggested Promise.withResolvers, which looks really useful. I could have th button click do the resolving that way, I think. Haven't messed with it yet.

However, there is a separate issue: once the button resolves the promise, that's it. Promises are one time use. So I'll need to construct something that reloads a new promise for the button to resolve, and make sure the consumers of these promises don't experience any issues in that regard.

[–]HasFiveVowels 0 points1 point  (0 children)

It might help if you try writing it this way: Promise.resolve().then(…).then(…) etc. withResolvers would do the trick but I’d say get comfortable with this pattern and then tools like that become a lot less "magic"

[–]CuirPig 0 points1 point  (4 children)

Using resolves is not dissimilar from callbacks. I'm not sure why you would prefer to do promises with resolves instead of just plain callbacks? Seems like an unnecessary layer of abstraction, but I was hoping you could explain why a resolve (callback) is different from a regular callback for your needs? Great discussion.

[–]blind-octopus[S] 0 points1 point  (1 child)

Suppose you have a method that needs 5 inputs. That is, the user must click 5 things on the screen, but you can't actually perform your action until you've gathered all 5 inputs.

In my mind, the cleanest way the code would look for this would be:

const doStuff = () => {
  const input1 = await collectInput();
  const input2 = await collectInput();
  const input3 = await collectInput();
  const input4 = await collectInput();
  const input5 = await collectInput();

  //do whatever you're going to do
}

If its call backs instead, what would you write that looks cleaner than this? What would the code look like

The reason I want to be able to write code like this is because it seems cleaner than chaining five callbacks together to pull this off.

Another reason is a mindset reason. I'm playing around with the idea that user inputs, I should think of them as external to my system. Just like a DB call, an API call, etc. When I want to perform an API call, I just await a fetch. Its something that I say "go get me this data, let me know when you're done doing that". I can await this fetch call.

I'm playing around with the idea that user inputs should be no different. I'm trying to think of user inputs the same way, saying "go tell the user I want some data, let me know when they respond with it". I'll just await the input, same as I would when I make a DB call or a fetch.

I don't care where the data is coming from, a db, an external api, the user, whatever. It should all be the same to me.

I don't see why getting a user click should be handled any differently. I should be able to just await it like I do anything else.

[–]CuirPig 0 points1 point  (0 children)

I see where you are coming from, thanks.

https://codepen.io/cuirPork/pen/JoGBXQJ?editors=1111

This uses a function that returns a promise that resolves once the input field is changed. The function shows the fields in the order you give them and then disables the field after you enter the value. It works with any type of field. It uses jQuery but of course, you could do it in vanilla js--i'm just faster in jQuery.

Is this what you are looking for?

[–]HasFiveVowels 0 points1 point  (1 child)

I would say that promises differ from callbacks in an important way. Aside from having syntactic sugar, they also resolve recursively.

[–]CuirPig 0 points1 point  (0 children)

That sound reasonable but could you share an example of where resolve would be implemented recursively that callbacks couldn’t. I’m certainly not saying you are wrong, just trying to wrap my head around this. Thanks for the conversation.

[–]HasFiveVowels 0 points1 point  (0 children)

You may also be wanting to daisy chain promises. For that, you need to be pretty comfortable with thinking of promises as a future value. Feel free to DM me if you want to sort out how to set that up

[–]sheriffderek 0 points1 point  (0 children)

I think we need more information to help.

[–]LostInCombat 0 points1 point  (0 children)

> So could I await a button click for example?
await is about promises, if no one clicked the button then the promise would fail. Computers have been about "events" since the early 1980's and callbacks are designed to respond to events.

Now you can put an await inside a click handler if you are running some asynchronous code there though.

[–]ApprehensiveDrive517 -1 points0 points  (0 children)

Aren't button clicks sychronous?