Search code examples
javascriptasync-awaitlaravel-livewirealpine.js

How to set a JS variable depending on the duration of an asynchronous call


I have this:

this.toggleWaiting()
this.results = await this.query(term)
this.toggleWaiting()

First a loading spinner gets triggered. Then the query runs. And when the query function ended the loading spinner gets closed.

But what if I want to just show the loading spinner, when the query takes maybe more then 0.5 seconds?

Is there a simple way to do this?


Solution

  • A way to achieve this is to pass the this.query(term) promise to a function which will handle triggering the toggleWaiting only when the query takes longer than the specified amount of time (using the timeout).

    For example, the below takes a promise, a function (waitingFn) which will be called with the isWaiting status as well as a timeout which you can use to specify how long you want to wait before you show the loading spinner. Finally, when the promise has been fulfilled, we return the result:

    async function handleWaiting(promise, waitingFn, timeout) {
      let loadingStarted = false;
      let timeoutInstance = null;
    
      const timeoutPromise = new Promise((res) => {
        timeoutInstance = setTimeout(() => {
          loadingStarted = true;
          waitingFn(true);
        }, timeout);
        return res();
      });
    
      function onFinished() {
        clearTimeout(timeoutInstance);
    
        if (loadingStarted) {
          waitingFn(false);
        }
      }
    
      try {
        const [result] = await Promise.all([promise, timeoutPromise]);
        onFinished();
        return result;
      } catch (ex) {
        onFinished();
        throw ex;
      }
    }
    

    You can call the handleWaiting function like so:

    const result = await handleWaiting(this.query(term), (isWaiting) => this.toggleWaiting(), 500);
    

    As @FZs and @Bergi have pointed out (thank you both), the below is an antipattern due to using the promise constructor:

    function handleWaiting(promise, waitingFn, timeout) {
      return new Promise((res, rej) => {
         let loadingStarted = false;
       
         const timeoutInstance = setTimeout(() => {
           loadingStarted = true; 
           waitingFn(true);
         }, timeout);
    
         function onFinished() {
           if (loadingStarted) {
             waitingFn(false);
           }
           clearTimeout(timeoutInstance);
         }
         
         return promise
           .then((result) => {
             onFinished();
             res(result);
           })
           .catch((ex) => {
             onFinished();
             rej(ex);
           });
      });
    }