Ionic2 responsiveness during heavy processing

I am building an Ionic 2 app that uses Ionic Native Contacts to read out the phone’s phonebook, save it in an Array of objects and then loops over each one of them to do some validations and convert them to the international E.164 format (e.g. +12011231234) using Google’s libphonenumber. On my phone this process takes around 6 seconds for processing roughly a thousand contacts, during which time Ionic becomes virtually unresponsive.

I understand that the Javascript code runs on a single thread and was wondering what the best way would be to handle this in Ionic 2. I came across this post and was wondering whether the requestAnimationFrame as is suggest in the post is the way to go for Ionic 2. Does anyone have experience or recommendations for me?

Basically I have two cases where I want to run this piece of code:

  1. the first time the app is launched where the phonebook needs to be processed and I don’t have anything cached yet. during this time it should run as fast as possible (maybe it is ok to be ‘stuck’ during this period if I show a loader)
  2. every time the app launches but I already have a cached version available and I would like to update the cached version by re-reading the phonebook in the background while the user is already using the app, so it is important that it is responsive (and I don’t mind if they are using a slightly outdated cached version of the phonebook)

By the way I will be happily listen to any other advice that may lead to performance improvements for this chunk of code. Anyway, this is the piece of code that I have now as part of a phonebook model class:

public cachePhoneBook() {
  return new Promise((resolve, reject) => {
  let contactsFound:Array<any> = [];

  /* get all contacts from phonebook */ 

  Contacts.find(this.contactFields,this.contactOptions).then((result) => {
    for (let contact of result) {

      /* logic looping through contact phone numbers and check for validity and duplicates */

    }
    this.session.cachedPhonebook = contactsFound;
    resolve();
  }, (error) => {
    reject(error);
  });
});

One thing you could try is breaking the loop into fixed-size chunks and running each chunk as a separate promise. This seems like it would be a perfect situation for using web workers, but I don’t think Ionic has official support for that yet.

Thanks @rapropos! Never heard of Web Workers before but it works out pretty well! After reading up on the topic a little bit, I understand that there are some projects that aim to run Ionic completely as a web worker. What I did however, was to just move my ‘heavy work’ above to a web worker (in plain Javascript) and it work great! Not only does the UI not get stuck at all, it actually runs the code a little faster than in the single thread before :slight_smile:

It looks something like this:

public cachePhoneBook() {
  return new Promise((resolve, reject) => {

  /* get all contacts from phonebook */ 

  Contacts.find(this.contactFields,this.contactOptions).then((result) => {
    for (let contact of result) {

      /* create web worker to process phonebook */
    
    let webWorker = new Worker('js/cache-phonebook-worker.js'); // relative to the www folder
    webWorker.postMessage({ phonebook: result });

    webWorker.onmessage = (event) =>  {
      this.session.cachedPhonebook = event.data;
      resolve();
    }
  }, (error) => {
    reject(error);
  });
});

My worker then looks something like this:

"use strict";

// import 3rd party libraries - path is relative to the main script (www/js)
importScripts('./libphonenumber.js'); 

self.onmessage = function (event) {
  var contactsFound = []; // contacts found while creating cached phonebook

  for (var _i = 0, _a = event.data.phonebook; _i < _a.length; _i++) {
    var contact = _a[_i]; // current contact in for loop

    /* logic looping through contact phone numbers and check for validity and duplicates */

  }

  postMessage(contactsFound); // return data to main script that invoked the worker
}

Now going to upgrade to RC0 and see how much the AoT can shave off the startup time :slight_smile:

Nice work on the web worker! Glad it’s working for you.

This is a classic JS problem, and if you’re not using web workers, chunking as @rapropos mentions is the best way to do this. Basically, you’d process a chunk, then let the UI do some work by calling the next loop through setTimeout or requestAnimationFrame.