typescriptasync-await

Can you tell if an object is awaiting an async member function?


I'm working on the client-side of a web application using TypeScript.

I have my own "framework" where I've developed a class (ViewBinder) that uses an async "show" method to load information from the server, extract HTML, and insert it into the document within a parent "placeholder" element. Its derived classes can then "bind" data from a client state object to the loaded HTML.

Here is some of the code from that class for reference (not an executable example):

export class ViewBinder extends PropertyChangedNotifier {
    // Lots of member stuff not shown for brevity...

    public async show(callbackAfterShow?: (wasSuccessful: boolean) => void): Promise<HtmlLoadViewResult> {
        this.showWasCanceled = false;
        const loadResult = await this.loadView();
        if (this.showWasCanceled) {    // Helps my situation some but not fully
            // If this.clear() was called during loadView, we just move on...
            loadResult.wasCanceled = true;
        } else {
            // HERE is where I can have a problem: if this.clear() is called
            // while a derived class is doing its setupAfterShowing, the underlying
            // HTML will be removed and the attempt to setup (binding data to the
            // HTML) will cause an unwanted error.
            await this.setupAfterShowing(loadResult);
            // CODE REMOVED FOR BREVITY
        }
        return loadResult;
    }

    public clear(): void {
        // If the show function is awaiting anything, tell it it was canceled
        // BUT: what I WANT to do is somehow wait until show() is done before
        // continuing the execution of this function... see the question text
        this.showWasCanceled = true;
        this.localViewBinders.forEach((viewBinder) => viewBinder?.clear());
        this.localBindingContexts.forEach((context) => context?.clear());
        if (this.parentElement) {
            this.parentElement.innerHTML = "";
            if (this.isModalDialog) {
                this.parentElement.remove();
            } else {
                this.collapse();
            }
            this.parentElement = null;  // We are no longer associated with the element
            this.notifyPropertyChanged("visibility");
            this.removeAllPropertyChangedListeners();
        }
    }

}

Note that derived classes will develop setupAfterShowing methods that "bind" data from the client state object to the HTML that has been loaded (and can call show on their own "child" ViewBinders). There is also a clear command that removes all the HTML from the parent element and clears out all the "data bindings" as well.

However, there are rare times when the underlying client state object changes while a ViewBinder is awaiting the show command (during the loadView or setupAfterShowing calls), and those changes cause the code to call the clear method, removing the body of the parent element where it was supposed to insert it's HTML and bind the data.

In general, if the ViewBinder doesn't find a parent element or can't find a place in that element to display data, I consider that a bug and I throw an error. However, in this case the HTML will be removed "legitimately" while the async code is awaiting results.

Note that I've tried to use showWasCanceled to avoid this issue, but there are too many cases of potential issues inside derived setupAfterShowing methods to ensure I'm always aborting if showWasCanceled is set to true.

Here is my question:

Is there a way for my clear function determine if the show function is "being awaited" and pause execution until "show" is done?


Solution

  • One way of doing this is create some fields to hold such info, e.g.

    private promise;
    private resolve;
    private reject;
    
    init() {
      this.promise = new Promise((res, rej)=>{
        this.resolve = res;
        this.reject= rej;
      });
    }
    
    public async show(...) {
      ...
      resolve(null); // or reject if any error thrown above
    }
    
    public async clear() {
      await this.promise;
      ...
    }