Search code examples
vue.jsasynchronousvue-component

[Vue3]: Asynchronous Non-Blocking Component Rendering Such That Render View (Page DOM) Ahead of View Model


I have an SPA with many components, some of the components contain long tasks(~ 6 seconds). when navigating through the app, it looks hanging (because of long tasks). To resolve the problem I have used Async Component. However, No difference and the result was the same as before. Also, Tried script setup to improve performance but it also didn't work.

The Problem

In the components included long tasks, View(DOM) and View Model(Data) are loaded both at the same time. and the SPA looks hanging through navigations

What I am looking for

Load View + No Data (Page Skin). afterward, push data(view model) into view when data is ready.

Code

await init() takes ~6 seconds to be completed. event though, I execute it onMounted it blocks view until it completed and view and data both are loaded at the same time.

<script setup>
// ...
onMounted(async () => {
  console.log('overview component mounted')
  await init()
  isMounted = true
})
</script>

Solution

  • In case you want to make the UI responsive if your tasks manipulate DOM there's no way to improve that, you should make the tasks shorter, for example instead of manipulating DOM compose pure HTML and insert at once. If you do long calculations that could be greatly improved with Workers.

    But if you want the component rendered then a long task executed (with hanging the UI) you should use setTimeout() to allow the browser to render the component. async/await uses microtasks that are executed in the end of the current macrotask but before the browser re-renders the page. setTimeout() queues a new macrotask thus allowing the browser re-render the updated DOM.

    You can read about microtasks here:
    https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide/In_depth

    <script setup>
    // ...
    onMounted(() => {
      console.log('overview component mounted')
      setTimeout(() => {
        init();
        isMounted = true
      });
      
    })
    </script>
    

    Edited by OP

    I used Web Worker as it was suggested by @Alexander Nenashev. Long running tasks are executed in a dedicated worker that is run in a new thread. Therefore, the UI is not blocked by long tasks and remains responsive. And also, the UI is updated when tasks are finished and data is ready.

    For me it was the ultimate solution.