0

I've got a nativescript vue application, and on a certain page I need to fetch some data (every 3 seconds) from an api using axios. The data is returned in xml, and I convert it to json using xml2js. I'm using async/await for both these functions. Something is blocking the UI thread, because whenever this function runs scrolling in my app freezes, along with any animations.

Does anyone know what here is blocking the DOM?

<template>
  //ui code here
</template>

<script>
import { mapGetters } from 'vuex'
import axios from 'axios'
import xml2js  from 'nativescript-xml2js'

export default {
  name: 'component1',
  data () {
    return {
      pending: true,
      error: false,
      results: null,
      refreshInterval: null
    }
  },
  computed: {
    ...mapGetters({
      token: 'main/token'
    })
  },
  methods: {
    async requestData() {

      const headers = {
        Cookie: 'USER_ID=' + this.token
      }
      const url = 'url goes here'
      const parser = new xml2js.Parser({
        async: true
      })

      try {
        const response = await axios.get(url, { headers: headers })

        const newData = await new Promise((resolve, reject) => parser.parseString(response.data, (err, result) => {
          if (err) reject(err)
          else resolve(result)
        }))

        this.results = newData['results']
        this.error = false
        this.pending = false

      } catch (e) {
        this.data = null
        this.error = e
        this.pending = false
      }
      this.pending = false
    }
  },
  created() {
    setTimeout(() => {
      this.requestData()
    },500)
    this.refreshInterval = setInterval(() => {
      this.requestData()
    },3000)
  },
  beforeDestroy () {
    clearInterval(this.refreshInterval)
  }
}
</script>

EDIT: I tried implementing workers to offload xml2js to another thread, but still having the same issue. This is how my code looks now:

home.vue:

<template>
<template/>

<script>
import { mapGetters } from 'vuex'

export default {
  name: 'component1',
  data () {
    return {
      dataLoaded: true,
      error: false,
      results: null,
      refreshInterval: null
    }
  },
  computed: {
    ...mapGetters({
      token: 'main/token'
    })
  },
  methods: {
    requestData() {

      console.log('fetching....')

      this.$backendService
        .api()
        .then(xml => {
          return this.$backendService.x2jworker(xml)
        })
        .then(json => {
          if( this.results !== json['results'] ) {
            this.results  = json['results']
          }
          this.dataLoaded = true
          this.error = false
        })
        .catch((error) => {
          this.dataLoaded = true
          this.error = true
        })
    }
  },
  created() {
    setTimeout(() => {
      this.requestData()
    },500)
    this.refreshInterval = setInterval(() => {
      this.requestData()
    },3000)
  },
  beforeDestroy () {
    clearInterval(this.refreshInterval)
  }
}
</script>

backend-service.js:

import axios from 'axios';
import xml2js from 'nativescript-xml2js'
import { WorkerService } from "../worker.service"

export default class BackendService {

  api() {
    return new Promise((resolve, reject) => {
      const url = 'url'
      axios.get(url)
        .then(response => {
          resolve(response.data)
        })
        .catch((error) => {
          if (error) {
            console.log('uh oh')
            reject(error)
          }
        })
    })
  }

  x2jworker(xml) {
    return new Promise((resolve, reject) => {
      var workerService = new WorkerService()
      var jsWorker = workerService.initJsWorker()
      jsWorker.onmessage = m => {
        resolve(m.data)
      }
      jsWorker.postMessage(xml)
      jsWorker.onerror = e => {
        console.log(e)
        jsWorker.terminate()
        reject(e)
      }
    })
  }
}

worker/javascript.worker.js:

import 'tns-core-modules/globals'
import xml2js  from 'nativescript-xml2js'

global.onmessage = function (msg) {
    console.log("Inside JS worker...")
    var parser = new xml2js.Parser({
      async: true
    })

    parser.parseString(msg.data, function (err, result) {
      if (err) {
        console.log(err)
        global.close()
      } else {          
        global.postMessage(result)
      }
    })
}

worker-service.js:

const workers = []

export class WorkerService {
  constructor() {
  }

  initJsWorker() {
    if (this.jsWorker) {
      return this.jsWorker
    }

    const JsWorker = require("nativescript-worker-loader!./workers/javascript.worker.js")
    this.jsWorker = new JsWorker()
    workers.push(this.jsWorker)

    return this.jsWorker
  }
}

if ((module).hot) {
  (module).hot.dispose(() => {
    workers.forEach(w => {
      w.terminate()
    })
  })
}
greenerr
  • 119
  • 3
  • 10
  • That is expected. JS and DOM share the same thread, and though the actual server trip is done in a separate thread, the results are handled in the same thread as the DOM runs. – Teemu Feb 28 '20 at 19:02
  • Oh, really? So there's no way to do a server call without blocking the DOM? – greenerr Feb 28 '20 at 19:04
  • Yes, that's the case, handling the received data will always block the DOM actions and other scripts. You could try to handle the data in parts asynchronously, then the delays in the other parts of the page actions become shorter. – Teemu Feb 28 '20 at 19:12
  • Oh, sorry I misunderstood. So it's the results handling, specifically xml2js, that is the problem. I could maybe do that processing on another server.... Not sure what other options there are. – greenerr Feb 28 '20 at 19:36
  • Yep, the time the actual request takes doesn't matter, browsers use a separate thread for that. If the data is not huge, you could also try to use a web worker to handle it. But this really works only with a small amount of data, otherwise the message handling takes even more time than the actual data handling. – Teemu Feb 28 '20 at 19:39
  • xml2js could be heavy process, you may try using a worker so it will not block the UI thread. Moreover I personally wouldn't recommend hitting APIs every 3 seconds, if you are doing this to refresh your data on screen you should probably think of using sockets, indeed it is quite a effort. – Manoj Feb 28 '20 at 20:03
  • Yea, the problem is that it's a 3rd party api. The xml files contain chat messages, so they recommend hitting them every 3 to 5 seconds. I just using xml2js in another thread with workers as per this method: https://docs.nativescript.org/core-concepts/multithreading-model, but I'm still getting the same jitters/stutters so the main thread is still getting blocked somehow. – greenerr Feb 28 '20 at 20:46
  • Please share a sample project where the issue can be reproduced. If you are unable to share one for whatever reason, you will have to use tools like timeline or android monitor or ios instruments to see where & when exactly the lag starts. – Manoj Feb 28 '20 at 20:59
  • You actually could use a [worker](https://developer.mozilla.org/en-US/docs/Web/API/Worker). Send the request from the worker, handle the results and postmessage the data in small chunks to the main page (if there's a lot of data). There are some limits when using XHR in workers, you can read about the limits on the MDN page I've linked. You might need also to append the data to the DOM in small parts, depending on how much content there is to add. – Teemu Feb 28 '20 at 21:54
  • @Manoj it's hard to share the app because you wouldn't be able to log in to access the pages I'm having trouble with. I tried to make a nativescript playground sample project using dummy data to replicate the issue, but the sample I made seems to work fine... Which makes no sense to me. playground is here if you want to take a look: https://play.nativescript.org/?template=play-vue&id=XxiLfl&v=5. But now I'm struggling to think of where else the issue could be. It's definitely related to xml2js, because when data returned is unchanged and I dont parse it, theres no problem – greenerr Feb 29 '20 at 00:45
  • I edited my post detailing how I tried to implement workers – greenerr Feb 29 '20 at 00:57

0 Answers0