2

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>


Nimantha
  • 6,405
  • 6
  • 28
  • 69

1 Answers1

1

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));
Nguyễn Văn Phong
  • 13,506
  • 17
  • 39
  • 56
  • 1
    Thank you! The code is working but it increases the loading time inside the vue app. It behaves somehow the following way: The pivot table component gets rendered within 15 seconds. Afterwards the progress bar component is displayed with the "loading animation" which takes also 15 seconds. The data for the pivot tables is loaded and set reactively into the pivot tables which takes about 2 seconds. So all in all about 32 seconds loading time. What i actually need is that the progress bar is displayed during the rendering process of the pivot vue component. – Denis Berkovich Apr 05 '21 at 11:15