enfrentando problemas de rendimiento con el complemento de mapeo knockout

I have decent large data set of around 1100 records. This data set is mapped to an observable array which is then bound to a view. Since these records are updated frequently, the observable array is updated every time using the ko.mapping.fromJS helper.

This particular command takes around 40s to process all the rows. The user interface just locks for that period of time.

Aquí está el código:

var transactionList = ko.mapping.fromJS([]);

//Getting the latest transactions which are around 1100 in number;
var data = storage.transactions();

//Mapping the data to the observable array, which takes around 40s
ko.mapping.fromJS(data,transactionList)

Is there a workaround for this? Or should I just opt of web workers to improve performances?

preguntado el 06 de septiembre de 12 a las 18:09

4 Respuestas

Knockout.viewmodel is a replacement for knockout.mapping that is significantly faster at creating viewmodels for large object arrays like this. You should notice a significant performance increase.

http://coderenaissance.github.com/knockout.viewmodel/

Respondido el 26 de diciembre de 12 a las 22:12

I will need some time to see if it actually improves the performance. Could you please give a sample code to depict how can I use it in my case particularly? - tusharmath

I have also thought of a workaround as follows, this uses less amount of code-

var transactionList = ko.mapping.fromJS([]);

//Getting the latest transactions which are around 1100 in number;
var data = storage.transactions();

//Mapping the data to the observable array, which takes around 40s
// Instead of - ko.mapping.fromJS(data,transactionList)
var i = 0;

//clear the list completely first
transactionList.destroyAll();

//Set an interval of 0 and keep pushing the content to the list one by one.
var interval = setInterval(function () {if (i == data.length - 1 ) {
                                        clearInterval(interval);}

                                    transactionList.push(ko.mapping.fromJS(data[i++]));
                                }, 0);

Respondido el 07 de Septiembre de 12 a las 10:09

I had the same problem with mapping plugin. Knockout team says that mapping plugin is not intended to work with large arrays. If you have to load such big data to the page then likely you have improper design of the system.

The best way to fix this is to use server pagination instead of loading all the data on page load. If you don't want to change design of your application there are some workarounds which maybe help you:

  1. Map your array manually:

    var data = storage.transactions();
    var mappedData = ko.utils.arrayMap(data , function(item){
        return ko.mapping.fromJS(item);
    });
    
    var transactionList = ko.observableArray(mappedData);
    
  2. Map array asynchronously. I have written a function that processes array by portions in another thread and reports progress to the user:

    function processArrayAsync(array, itemFunc, afterStepFunc, finishFunc) {
        var itemsPerStep = 20;
    
        var processor = new function () {
            var self = this;
            self.array = array;
            self.processedCount = 0;
            self.itemFunc = itemFunc;
            self.afterStepFunc = afterStepFunc;
            self.finishFunc = finishFunc;
            self.step = function () {
                var tillCount = Math.min(self.processedCount + itemsPerStep, self.array.length);
                for (; self.processedCount < tillCount; self.processedCount++) {
                    self.itemFunc(self.array[self.processedCount], self.processedCount);
                }
    
                self.afterStepFunc(self.processedCount);
                if (self.processedCount < self.array.length - 1)
                    setTimeout(self.step, 1);
                else
                    self.finishFunc();
            };
        };
    
        processor.step();
    };
    

Tu codigo:

var data = storage.transactions();
var transactionList = ko.observableArray([]);

processArrayAsync(data,
    function (item) { // Step function
        var transaction = ko.mapping.fromJS(item);
        transactionList().push(transaction);
    },
    function (processedCount) { 
        var percent = Math.ceil(processedCount * 100 / data.length);
        // Show progress to the user.
        ShowMessage(percent);
    },
    function () { // Final function
        // This function will fire when all data are mapped. Do some work (i.e. Apply bindings).
    });

Also you can try alternative mapping library: knockout.wrap. It should be faster than mapping plugin.

I have chosen the second option.

Respondido 21 ago 13, 02:08

Very interesting solution. Would be interesting to create a JsPerf to see how much faster it can get. - porra

@billy Please be careful when editing posts. Changing coding style of the original question is considered the same as changing the meaning of the post. I rolled back the edit. - Lundín

Here is the jsperf: jsperf.com/… (I am the author of the custom mapper that is being tested) - Aymeric Gaurat-Apelli

Mapping is not magic. In most of the cases this simple recursive function can be sufficient:

function MyMapJS(a_what, a_path)
{
    a_path = a_path || [];

    if (a_what != null && a_what.constructor == Object)
    {
        var result = {};
        for (var key in a_what)
           result[key] = MyMapJS(a_what[key], a_path.concat(key));
        return result;
    }

    if (a_what != null && a_what.constructor == Array)
    {
        var result = ko.observableArray();
        for (var index in a_what)
           result.push(MyMapJS(a_what[index], a_path.concat(index)));
        return result;
    }

    // Write your condition here:
    switch (a_path[a_path.length-1])
    {
        case 'mapThisProperty':
        case 'andAlsoThisOne':
            result = ko.observable(a_what);
            break;
        default:
            result = a_what;
            break;
    }
    return result;
}

The code above makes observables from the mapThisProperty y andAlsoThisOne properties at any level of the object hierarchy; other properties are left constant. You can express more complex conditions using a_path.length for the level (depth) the value is at, or using more elements of a_path. Por ejemplo:

if (a_path.length >= 2 
  && a_path[a_path.length-1] == 'mapThisProperty' 
  && a_path[a_path.length-2] == 'insideThisProperty')
    result = ko.observable(a_what);

Puede usar el typeOf a_what in the condition, e.g. to make all strings observable. You can ignore some properties, and insert new ones at certain levels. Or, you can even omit a_path. Etc

Las ventajas son:

  • Customizable (more easily than knockout.mapping).
  • Short enough to copy-paste it and write individual mappings for different objects if needed.
  • Smaller code, knockout.mapping-latest.js is not included into your page.
  • Should be faster as it does only what is absolutely necessary.

Respondido el 16 de enero de 15 a las 10:01

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.