500 ms to 5 ms
So, 14.10.2017 - 11:37
This is short example about Observables and how they can be really evil. Fortunately we have a hero called [Object.freeze(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FGlobal_Objects%2FObject%2Ffreeze). ## Problem Same as in [previous article about Web Workers](/node/13), we have large data sets, several objects that contain arrays of thousands values. This time it's not about processing, sorting, filtering these arrays, but just about storing it to [vuex](https://vuex.vuejs.org/en/) store. Structure looks like this: ```js // pseudocode cars: { car1: { visible: Boolean(), metrics: Object(), trails: Array(30), values: Array(8000)[ {available: Number, lat: String, lng: String}, ... ] }, ... }, ... ``` Execution of setter for cars `state.cars = cars` took around ~500 ms, which is quire a lot, when web becomes unresponsive for half second. And JS heap jumped to 270 MB, nobody wants that. We can see, most of execution time is taken by `reactiveSetter` executed from `setCars`.What can I do with this? I know data won't change, it's loaded from API, and if it refreshes, whole `car` object gets refreshed. How to prevent those arrays to be observable? ## Solution It's simple, just get rid of those expensive Observables. You could just do this ```js commit('setCars', Object.freeze(cars)) ``` Now we are down from 500 ms to 0.15 ms, ~3000 times faster and with JS Heap dropped to ~67 MB. But there is little problem. We have key `visible`, that needs to be changeable. And that won't work with whole Object frozen. ``` TypeError: Cannot assign to read only property 'visible' of object '#' ``` So we freeze just parts of Object we need. ```js const frozenCars = {} Object.keys(cars).forEach(key => { frozenCars[key] = { ...cars[key], metrics: Object.freeze(cars[key].metrics), trail: Object.freeze(cars[key].trail), values: Object.freeze(cars[key].values) } }) commit('setCars', frozenCars) ``` Ok, now we've added a bit of complexity, forEeach takes ~4 ms + 0.15 ms setCars, but still compared to 500 ms, we are good, 100 times faster and with interactive functionality. ## Conclusion Save time and memory with `Object.freeze(obj)`, not everything needs to be observable. ## Notes You might wonder, why not to freeze data when we are transforming it (in Web Worker) and save those ~4 ms in forEach. ```js cars[car.metric.name] = { trail: Object.freeze(newCar.trail), visible: newCar.visible, metric: Object.freeze(newCar.metric), values: Object.freeze(newCar.values) } ``` This would work nicely, if we had this on main thread. But we are sending data from worker to main with postMessage which uses [Structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm), so it would "unfreeze" objects. You can find non-frozen code on github [ra100/carshare-exporter-viz example/nofreeze branch](https://github.com/ra100/carshare-exporter-viz/blob/example/nofreeze/src/store/actions.js).