0

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?

Slowwie
  • 1,146
  • 2
  • 20
  • 36

2 Answers2

0

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);
       });
  });
}
ljbc1994
  • 2,044
  • 11
  • 13
  • 1
    This code has the [Explicit Promise construction antipattern](https://stackoverflow.com/q/23803743/8376184)! You should use chaining and promise composition instead... Also, a function that returns a promise, but doesn't await anything don't have to be `async` – FZs Dec 12 '20 at 06:19
  • Avoid the [`Promise` constructor antipattern](https://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it)! – Bergi Dec 12 '20 at 10:46
0

Thanks to ljbc1994 I found a nice solution.

Inside my alpineJs object - I have this implementation:

{
   waiting: false,

   async handleWaiting(promise, timeout) {
       return new Promise((res, rej) => {
           let loadingStarted = false;

           const timeoutInstance = setTimeout(() => {
               loadingStarted = true;
               this.waiting = true;
           }, timeout);

           const onFinished = () => {
               if (loadingStarted) {
                   this.waiting = false;
               }
               clearTimeout(timeoutInstance);
           }

           promise
               .then((result) => {
                   onFinished();
                   res(result);
               })
               .catch((ex) => {
                   onFinished();
                   rej(ex);
               });
       });
    },

    async searchForTerm(term) {
       this.results = await this.handleWaiting(this.$wire.query(term), 500);
       // do something with the results...
    },
 }

Pretty straightforward.

For someone who is interested in the full code - here is the commit inside the github repo:

https://github.com/MichaelBrauner/sunfire-form/commit/7c1f8270e107a97b03264f5ddc5c3c3ae6f7cfd7

Slowwie
  • 1,146
  • 2
  • 20
  • 36
  • 2
    This code (as well as ljbc1994's) has the [Explicit Promise construction antipattern](https://stackoverflow.com/q/23803743/8376184)! You should use chaining and promise composition instead... Also, a function that returns a promise, but doesn't await anything don't have to be `async` – FZs Dec 12 '20 at 06:21
  • Ok. Sounds good. I like to write clean code. Thank you. Can you maybe give me a small code example, how to write it with the promise composition and without the async flag? – Slowwie Dec 12 '20 at 07:36
  • Hey, I've amended the answer to remove the antipattern – ljbc1994 Dec 12 '20 at 12:05
  • @Slowwie I wanted to write an answer to that when I had free time, but I'm glad that [you've already got an answer](https://stackoverflow.com/a/65264799/8376184) ;) – FZs Dec 12 '20 at 15:11