mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-23 11:16:39 +00:00
refactor: move memoize function to shared utils (#2090)
We move the memoize function to `lib/shared/utils.js` and expose it to modules across the project. Change-Type: patch Changelog-Entry: Move memoize function to shared utils.
This commit is contained in:
parent
68b33fcfb9
commit
a83e397643
@ -23,6 +23,7 @@ const constraints = require('../../../../../shared/drive-constraints')
|
|||||||
const analytics = require('../../../modules/analytics')
|
const analytics = require('../../../modules/analytics')
|
||||||
const availableDrives = require('../../../../../shared/models/available-drives')
|
const availableDrives = require('../../../../../shared/models/available-drives')
|
||||||
const selectionState = require('../../../../../shared/models/selection-state')
|
const selectionState = require('../../../../../shared/models/selection-state')
|
||||||
|
const utils = require('../../../../../shared/utils')
|
||||||
|
|
||||||
module.exports = function (
|
module.exports = function (
|
||||||
$q,
|
$q,
|
||||||
@ -173,66 +174,17 @@ module.exports = function (
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Memoize ImmutableJS list reference
|
* @summary Memoized getDrives function
|
||||||
* @function
|
* @function
|
||||||
* @private
|
* @public
|
||||||
*
|
*
|
||||||
* @description
|
* @returns {Array<Object>} - memoized list of drives
|
||||||
* This workaround is needed to avoid AngularJS from getting
|
|
||||||
* caught in an infinite digest loop when using `ngRepeat`
|
|
||||||
* over a function that returns a mutable version of an
|
|
||||||
* ImmutableJS object.
|
|
||||||
*
|
|
||||||
* The problem is that every time you call `myImmutableObject.toJS()`
|
|
||||||
* you will get a new object, whose reference is different from
|
|
||||||
* the one you previously got, even if the data is exactly the same.
|
|
||||||
*
|
|
||||||
* @param {Function} func - function that returns an ImmutableJS list
|
|
||||||
* @returns {Function} memoized function
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const getList = () => {
|
* const drives = DriveSelectorController.getDrives()
|
||||||
* return Store.getState().toJS().myList;
|
* // Do something with drives
|
||||||
* };
|
|
||||||
*
|
|
||||||
* const memoizedFunction = memoizeImmutableListReference(getList);
|
|
||||||
*/
|
*/
|
||||||
this.memoizeImmutableListReference = (func) => {
|
this.getDrives = utils.memoize(this.drives.getDrives, angular.equals)
|
||||||
let previousTuples = []
|
|
||||||
|
|
||||||
return (...restArgs) => {
|
|
||||||
let areArgsInTuple = false
|
|
||||||
let state = Reflect.apply(func, this, restArgs)
|
|
||||||
|
|
||||||
previousTuples = _.map(previousTuples, ([ oldArgs, oldState ]) => {
|
|
||||||
if (angular.equals(oldArgs, restArgs)) {
|
|
||||||
areArgsInTuple = true
|
|
||||||
|
|
||||||
if (angular.equals(state, oldState)) {
|
|
||||||
// Use the previously memoized state for this argument
|
|
||||||
state = oldState
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the tuple state
|
|
||||||
return [ oldArgs, state ]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the tuple unchanged
|
|
||||||
return [ oldArgs, oldState ]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Add the state associated with these args to be memoized
|
|
||||||
if (!areArgsInTuple) {
|
|
||||||
previousTuples.push([ restArgs, state ])
|
|
||||||
}
|
|
||||||
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.getDrives = this.memoizeImmutableListReference(() => {
|
|
||||||
return this.drives.getDrives()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Get a drive's compatibility status object(s)
|
* @summary Get a drive's compatibility status object(s)
|
||||||
@ -253,9 +205,9 @@ module.exports = function (
|
|||||||
* // do something
|
* // do something
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
this.getDriveStatuses = this.memoizeImmutableListReference((drive) => {
|
this.getDriveStatuses = utils.memoize((drive) => {
|
||||||
return this.constraints.getDriveImageCompatibilityStatuses(drive, this.state.getImage())
|
return this.constraints.getDriveImageCompatibilityStatuses(drive, this.state.getImage())
|
||||||
})
|
}, angular.equals)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Keyboard event drive toggling
|
* @summary Keyboard event drive toggling
|
||||||
|
@ -78,3 +78,62 @@ exports.percentageToFloat = (percentage) => {
|
|||||||
|
|
||||||
return percentage / exports.PERCENTAGE_MAXIMUM
|
return percentage / exports.PERCENTAGE_MAXIMUM
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Memoize a function
|
||||||
|
* @function
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* This workaround is needed to avoid AngularJS from getting
|
||||||
|
* caught in an infinite digest loop when using `ngRepeat`
|
||||||
|
* over a function that returns a mutable version of an
|
||||||
|
* ImmutableJS object.
|
||||||
|
*
|
||||||
|
* The problem is that every time you call `myImmutableObject.toJS()`
|
||||||
|
* you will get a new object, whose reference is different from
|
||||||
|
* the one you previously got, even if the data is exactly the same.
|
||||||
|
*
|
||||||
|
* @param {Function} func - function that returns an ImmutableJS list
|
||||||
|
* @param {Function} comparer - function to compare old and new args and state
|
||||||
|
* @returns {Function} memoized function
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const getList = () => {
|
||||||
|
* return Store.getState().toJS().myList;
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* const memoizedFunction = memoize(getList, angular.equals);
|
||||||
|
*/
|
||||||
|
exports.memoize = (func, comparer) => {
|
||||||
|
let previousTuples = []
|
||||||
|
|
||||||
|
return (...restArgs) => {
|
||||||
|
let areArgsInTuple = false
|
||||||
|
let state = Reflect.apply(func, this, restArgs)
|
||||||
|
|
||||||
|
previousTuples = _.map(previousTuples, ([ oldArgs, oldState ]) => {
|
||||||
|
if (comparer(oldArgs, restArgs)) {
|
||||||
|
areArgsInTuple = true
|
||||||
|
|
||||||
|
if (comparer(state, oldState)) {
|
||||||
|
// Use the previously memoized state for this argument
|
||||||
|
state = oldState
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the tuple state
|
||||||
|
return [ oldArgs, state ]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the tuple unchanged
|
||||||
|
return [ oldArgs, oldState ]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add the state associated with these args to be memoized
|
||||||
|
if (!areArgsInTuple) {
|
||||||
|
previousTuples.push([ restArgs, state ])
|
||||||
|
}
|
||||||
|
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -20,6 +20,7 @@ const _ = require('lodash')
|
|||||||
const m = require('mochainon')
|
const m = require('mochainon')
|
||||||
const angular = require('angular')
|
const angular = require('angular')
|
||||||
require('angular-mocks')
|
require('angular-mocks')
|
||||||
|
const utils = require('../../../lib/shared/utils')
|
||||||
|
|
||||||
describe('Browser: DriveSelector', function () {
|
describe('Browser: DriveSelector', function () {
|
||||||
beforeEach(angular.mock.module(
|
beforeEach(angular.mock.module(
|
||||||
@ -27,64 +28,9 @@ describe('Browser: DriveSelector', function () {
|
|||||||
))
|
))
|
||||||
|
|
||||||
describe('DriveSelectorController', function () {
|
describe('DriveSelectorController', function () {
|
||||||
let $controller
|
describe('.memoize()', function () {
|
||||||
let $rootScope
|
|
||||||
let $q
|
|
||||||
let $uibModalInstance
|
|
||||||
let WarningModalService
|
|
||||||
|
|
||||||
let controller
|
|
||||||
|
|
||||||
beforeEach(angular.mock.inject(function (
|
|
||||||
_$controller_,
|
|
||||||
_$rootScope_,
|
|
||||||
_$q_,
|
|
||||||
_WarningModalService_
|
|
||||||
) {
|
|
||||||
$controller = _$controller_
|
|
||||||
$rootScope = _$rootScope_
|
|
||||||
$q = _$q_
|
|
||||||
$uibModalInstance = {}
|
|
||||||
WarningModalService = _WarningModalService_
|
|
||||||
}))
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
controller = $controller('DriveSelectorController', {
|
|
||||||
$scope: $rootScope.$new(),
|
|
||||||
$q,
|
|
||||||
$uibModalInstance,
|
|
||||||
WarningModalService
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('.memoizeImmutableListReference()', function () {
|
|
||||||
it('constant true should return memoized true', function () {
|
|
||||||
const memoizedConstTrue = controller.memoizeImmutableListReference(_.constant(true))
|
|
||||||
m.chai.expect(memoizedConstTrue()).to.be.true
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should reflect state changes', function () {
|
|
||||||
let stateA = false
|
|
||||||
const memoizedStateA = controller.memoizeImmutableListReference(() => {
|
|
||||||
return stateA
|
|
||||||
})
|
|
||||||
|
|
||||||
m.chai.expect(memoizedStateA()).to.be.false
|
|
||||||
|
|
||||||
stateA = true
|
|
||||||
|
|
||||||
m.chai.expect(memoizedStateA()).to.be.true
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should reflect different arguments', function () {
|
|
||||||
const memoizedParameter = controller.memoizeImmutableListReference(_.identity)
|
|
||||||
|
|
||||||
m.chai.expect(memoizedParameter(false)).to.be.false
|
|
||||||
m.chai.expect(memoizedParameter(true)).to.be.true
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle equal angular objects with different hashes', function () {
|
it('should handle equal angular objects with different hashes', function () {
|
||||||
const memoizedParameter = controller.memoizeImmutableListReference(_.identity)
|
const memoizedParameter = utils.memoize(_.identity, angular.equals)
|
||||||
const angularObjectA = {
|
const angularObjectA = {
|
||||||
$$hashKey: 1,
|
$$hashKey: 1,
|
||||||
keyA: true
|
keyA: true
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
|
const _ = require('lodash')
|
||||||
const m = require('mochainon')
|
const m = require('mochainon')
|
||||||
const utils = require('../../lib/shared/utils')
|
const utils = require('../../lib/shared/utils')
|
||||||
|
|
||||||
@ -125,4 +126,31 @@ describe('Shared: Utils', function () {
|
|||||||
}).to.throw('Invalid percentage: 100.01')
|
}).to.throw('Invalid percentage: 100.01')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('.memoize()', function () {
|
||||||
|
it('constant true should return memoized true', function () {
|
||||||
|
const memoizedConstTrue = utils.memoize(_.constant(true), _.isEqual)
|
||||||
|
m.chai.expect(memoizedConstTrue()).to.be.true
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should reflect state changes', function () {
|
||||||
|
let stateA = false
|
||||||
|
const memoizedStateA = utils.memoize(() => {
|
||||||
|
return stateA
|
||||||
|
}, _.isEqual)
|
||||||
|
|
||||||
|
m.chai.expect(memoizedStateA()).to.be.false
|
||||||
|
|
||||||
|
stateA = true
|
||||||
|
|
||||||
|
m.chai.expect(memoizedStateA()).to.be.true
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should reflect different arguments', function () {
|
||||||
|
const memoizedParameter = utils.memoize(_.identity, _.isEqual)
|
||||||
|
|
||||||
|
m.chai.expect(memoizedParameter(false)).to.be.false
|
||||||
|
m.chai.expect(memoizedParameter(true)).to.be.true
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user