Running asynchronous setTimeout counter parallely to Promise.all() to display a progress loader in %

Viewed 184

I am having an issue running an asynchronous setTimeout counter parallel to a Promise.all() handler when trying to display a progress loader in %.

Here the details:

I've built a Vue app consisting of three components.

The first component "Test.vue" is importing a progress bar component and a pivot component which is containing about 12 pivot tables with data from different Google firestore collections (handled via Promise.all()). Currently, it takes about 15 seconds until the pivot component is rendered successfully. During this waiting period, I want to show a progress bar in % which goes up to 100% until all data for the pivot component is loaded and rendered successfully.

I was trying different approaches and currently, I am following the "easy" approach that the progress bar just shall display within 15 seconds the loading progress.

But even this approach doesn't work properly for me.

It seems to me like the progress functionality always waits until the loading and rendering of the pivot component is finished.

I really don't have any idea anymore how to solve this.

Which recommendations do you have?

Hint (if important): Inside the pivot component the data is loaded inside the mounted-hook via Promise.all()

Here the code for the Test.vue component:

Test.vue: 

<template>
  <mdb-container class="full-width" style="margin-top:35px !important;">
    <mdb-row v-if="tabChange">
      <mdb-progress :height="30" :value="value">{{value}} %</mdb-progress>
    </mdb-row>
    <mdb-card>
      <mdb-card-header>
        <mdb-tab default class="card-header-tabs">
          <mdb-tab-item 
            v-for="tab in tabs"
            v-bind:key="tab"
            v-bind:class="[{ active: currentTab === tab }]"
            :active="currentTab == tab"
            @click.native.prevent="currentTab=tab"
            >{{ tab }}</mdb-tab-item>
        </mdb-tab>
      </mdb-card-header>
        <mdb-card-body>
          <mdb-tab-content>
            <mdb-tab-pane class="fade">
              <mdb-row>
                <keep-alive>
                  <component v-bind:is="currentTabComponent" v-on:finished="setFinished" class="tab"></component>
                </keep-alive>
              </mdb-row>
            </mdb-tab-pane>
          </mdb-tab-content>
        </mdb-card-body>
    </mdb-card>
  </mdb-container>
</template>

<script>


/* eslint-disable */
import { mdbProgress, mdbContainer,  mdbRow, mdbCol, mdbBtn, mdbCard, mdbCardTitle, mdbCardText, mdbCardFooter, mdbCardBody, mdbCardHeader, mdbListGroup, mdbListGroupItem, mdbNavItem, mdbCardGroup, mdbIcon, mdbFooter, mdbTab, mdbTabItem, mdbTabContent, mdbTabPane } from 'mdbvue';

import { db } from '@/main'
import PivotTable from '@/components/flight-builder/PivotTable'
import DefaultFilters from '@/components/flight-builder/filters/DefaultFilters'
import MainTab from '@/components/flight-builder/ads-archive/MainTab'
import Channel from '@/components/flight-builder/ads-archive/Channel'
import Loader from '@/components/Loader'
//import excel from "vue-excel-export"

/*const Channel = () => ({
  component: import('@/components/flight-builder/ads-archive/Channel'),
  loading: LoadingComponent,
  error: LoadingComponent,
  delay: 200,
  timeout: 3000
})*/

export default {
  name: 'AdsArchivePage',
  components: {
    PivotTable,
    mdbContainer,
    mdbRow,
    mdbCol,
    mdbBtn,
    mdbCard,
    mdbCardTitle,
    mdbCardText,
    mdbCardFooter,
    mdbCardBody,
    mdbCardHeader,
    mdbListGroup,
    mdbListGroupItem,
    mdbNavItem,
    mdbCardGroup,
    mdbIcon,
    mdbFooter,
    mdbTab,
    mdbTabItem,
    mdbTabContent,
    mdbTabPane,
    mdbProgress,
    DefaultFilters,
    MainTab,
    Channel,
    Loader
  },
  data: () => {
    return {
      active: 0,
      currentTab: "Main Tab",
      value: 0,
      tabs: ["Main Tab", "Channel", "Flight", "AdType", "Creative", "Spot length"],
      componentMatcher: {
        "Main Tab": "",
        "Channel": Channel,
        "Flight": "",
        "AdType": "",
        "Creative": "",
        "Spot length": ""
      },
      finishedLoading: false
    }
 
  },
  methods: {
    setFinished(finishedLoading) {
      this.finishedLoading = finishedLoading
    },
    timeout(ms) { //pass a time in milliseconds to this function
        return new Promise(resolve => setTimeout(resolve, ms));
    },
    async wait() {
      let loadingTime = 15
      this.value = 0
      console.log("wait123")
      for (let i = 0; i<=loadingTime; i++) {
          //setTimeout(() => {this.value = Math.round((i / loadingTime)*100)}, 15000);
          this.value = Math.round((i / loadingTime)*100)
          this.$forceUpdate()
          await this.timeout(1000)
          //await sleep(1000);
      }
    }
  },
  computed: {
    currentTabComponent: function() {
      return this.componentMatcher[this.currentTab]
    },
    tabChange: function() {
      if (this.prevTab != this.currentTab) {
        this.wait()
        return true
      }
      return false
    }
  }
}
</script>


1 Answers

During this waiting period I want to show a progress bar in % which goes up to 100% until all data for the pivot component is loaded and rendered successfully.

I can tell you that the @Keith's answer did the trick like below to show all promises progress:

const allProgress = (proms, progress_cb) => {
  let d = 0;
  progress_cb(0); // Start progress_cb
  for (const p of proms) { // Interate all promises
    p.then(() => {    
      d++;
      progress_cb( (d * 100) / proms.length ); // Display each item when it's done.
    });
  }
  return Promise.all(proms);
}

const test = ms => new Promise((resolve) => {setTimeout(() => resolve(), ms);}); 

allProgress([test(1000), test(2000), test(3000)],
           p => console.log(`% Done = ${p.toFixed(2)}`));

As a result, you can adjust the progress_cb method based on your needs (Instead of just console.log)


Updated

The second solution is to use promiss.all. Basically, this one the same as naortor's answer but refactor code.

var count = 0;
const waitThenTrigger = p => p.then(val => { progress(++count); return val;}); 
const progress = count =>  console.log(`% Done = ${(count*100/promiseArray.length).toFixed(2)}`);
const createPromise = (ms, value) => new Promise((resolve) => {setTimeout(() => resolve(value), ms);});

var promiseArray = [
  waitThenTrigger(createPromise(3000, "a")),   
  waitThenTrigger(createPromise(2000, "b")),
  waitThenTrigger(createPromise(1000, "c"))];
Promise.all(promiseArray).then(values => console.log(values));

Related