0

(I don't think this is a duplicate of How do I make asynchronous calls in an event handler , even though the title is similar. It is also different from questions about disabling buttons, since there may be lots of different events that can cause asynchronous conflicts.)

When, while visiting my webpage, a button is clicked or a key is pressed, I want to be able to do asynchronous Promise chains, such as to read a database, to read a file, or to write a log entry. But these require leaving the event handler/listener, so it seems impossible to return a value from the original handler at the end of the 'then' chain.

This is okay, but the problem with returning from a handler synchronously (that is, immediately) is that the user might click the same button or press the key again, or even do an unrelated action, which might trigger a conflicting asynchronous operation (assuming that these operations cannot be done in parallel).

I guess I want to 'deaden' the event system while the asynchronous operations are in progress (with a timeout in case of unexpected failure to terminate, perhaps), but this feels dangerous. It surely can't be the right thing to do.

David Spector
  • 1,520
  • 15
  • 21
  • You should store some flag that asynchronous operation is running and block the button until it's done. Any way, I should see the code to understand the problem better. – Kyryl Stronko Aug 30 '19 at 13:41
  • Yes, an asynchronous call will "break" out of that event handler. There's not much you can do about that other than properly handling that situation using (for example) promises. – Cerbrus Aug 30 '19 at 13:41
  • Also a duplicate of: [How to disable a submit button when AJAX request is in progress and enable it after receiving success AJAX response?](https://stackoverflow.com/questions/24989475/how-to-disable-a-submit-button-when-ajax-request-is-in-progress-and-enable-it-af) – Cerbrus Aug 30 '19 at 13:46
  • 1
    Possible duplicate of [How to disable a submit button when AJAX request is in progress and enable it after receiving success AJAX response?](https://stackoverflow.com/questions/24989475/how-to-disable-a-submit-button-when-ajax-request-is-in-progress-and-enable-it-af) – KarelG Aug 30 '19 at 13:47
  • you should probably re-title your question - it's really about how to prevent _new_ async operations happening while one is already running. – Alnitak Aug 30 '19 at 13:49
  • This problem is not completely solved by disabling a button. What if another button creates an undetectable error condition where some of the same asynchronous operations are triggered concurrently? – David Spector Aug 30 '19 at 17:47
  • And what about allowing non-interfering user interaction to continue? – David Spector Aug 30 '19 at 20:38
  • So, a concrete example showing why disabling a button won't work is this: suppose one button affects the file system (we don't have to know what files) and another button affects only library variables. Then we can allow the user to click the "library variables" button while the "file system" button's operations are in progress. See my other comments about operation domains. I think this will be a better solution than a modal disabling of the entire GUI. I wish I had a forum that really worked to discuss this with others. – David Spector Aug 31 '19 at 00:39

2 Answers2

1

The only way I've found to handle this is to visibly (and explicitly) disable interaction.

One way is to put a full page semi-transparent modal div in front of all of the content that prevents further user events from starting new processes that might interfere with any ongoing background activities.

If your app is small enough that only a few elements might trigger new processes then explicitly disabling all of those might suffice instead.

Alnitak
  • 334,560
  • 70
  • 407
  • 495
  • Would the downvoter care to explain what they think is wrong with this, or propose a better method? In an async system, temporarily (and visibly) disabling user interaction is the best way to prevent multiple things from happening at the same time. – Alnitak Aug 30 '19 at 13:45
  • This solution, which was proposed in another answer, is actually pretty good, in that it does protect the data. However, it is extreme, in that it unnecessarily prohibits the user from doing other, non-interfering activities. There must be a way to specify the domain of an asynchronous task. Then the system could allow the task if its domain does not overlap (intersect) with the domain of another task already in progress. Domains could include variables and files, but that might not be enough state information. – David Spector Aug 30 '19 at 17:56
  • @DavidSpector that's why I also proposed (and actually my answer was first) that individual controls could be disabled. – Alnitak Aug 30 '19 at 20:45
  • But as for "domains of tasks", there's no such feature built-in. You usually need a state machine, and then each interactive element's `enabled` property should change automatically each time the machine's state changes. – Alnitak Aug 30 '19 at 20:47
  • I'm not sure a finite-state machine would fit into a website. But I am continuing to develop the idea of operation domain sets, which would be Sets of strings representing classes of operations such as "fileChanges" and "libraryVariables". My goal is to allow operations that do not have a nonnull intersection between – David Spector Aug 31 '19 at 00:33
  • their domain set and the current domain sets of running asynchronous threads. It's really not very complicated, and can be modified over time for finer granularity. For example, while a thread is running to generate a log/journal entry in a DB, the user could continue to enter information in a form. – David Spector Aug 31 '19 at 00:33
  • I've received an automatic warning about discussions in comments. Too bad they are discouraged, but I'll obey the rules here. Bye, all, I guess I'll solve this basic problem on my own. – David Spector Aug 31 '19 at 00:42
0

I do something like this (assuming jquery)

$button.on('click', async function() {
    $(this).prop('disabled', true);
    try {
        // async handling here
        await asyncFunction();
    } catch(e) {

    } finally {
        $(this).prop('disabled', false);
    }
})

Rather than setting disabled, you can also optionally set a variable that more button watchers can be aware of, to have more control over what things are or are not allowed to run while you're waiting.

Or set up a full-page loading modal that blocks everything.

Or even set pointer-events to none and make the cursor a loading cursor through css.

TKoL
  • 13,158
  • 3
  • 39
  • 73
  • Would this work with a true async call (without `await`)? The `finally` will be triggered when the async call is sent, not when it finishes.. – Kaddath Aug 30 '19 at 13:52
  • This works WITH await. To do it without await, with promises, you'd have to do a `.then().catch()` chain – TKoL Aug 30 '19 at 13:53
  • Native Promises have `.finally` though so you can structure it the same inside promise callbacks if you want to – TKoL Aug 30 '19 at 13:54
  • A Promise's `.finally` isn't anywhere close to the same as a `try/catch/finally` block, though. They're not compatible concepts. – Cerbrus Aug 30 '19 at 13:55
  • @Cerbrus I do'nt know what you mean. I just mean that you can set the 'disabled' prop to true before the promise starts, and then start the promise, `promiseFunc().then(do stuff here).finally(() => $(this).prop('disabled', false);)` I don't see why that wouldn't work. – TKoL Aug 30 '19 at 13:57
  • Yes, _that_ would work. What you have in your answer, though, isn't a `Promise`. – Cerbrus Aug 30 '19 at 13:57
  • No, which is why in my comment I said to use await, and in my answer I have the 'async' keyword on the callback function. Awaiting promises in a `try catch finally` absolutely works. – TKoL Aug 30 '19 at 13:58
  • I see, then it's probably a _very_ good idea to add that `await` as an example in your code. It's pretty crucial for your answer to function. – Cerbrus Aug 30 '19 at 14:06
  • Sure, thought it would be obvious enough by the `async` keyword that we're awaiting async functions, but there's no downside to showing it explicitly. – TKoL Aug 30 '19 at 14:13
  • I want to protect the asynchronous operations and the consistency of their data, not the buttons that trigger them. I was hoping for a better way from someone who has actually solved the non-interference problem. One possible solution might be to create Abelian asynchronous tasks. "Abelian" means that the task checks to see if it needs to run, then does. Two problems with this: (1) you can't always check to see if an operation is needed, (2) even if you can, there is a race condition where two users both see that the operation is needed, so it is triggered twice concurrently: disaster. – David Spector Aug 30 '19 at 17:53