Skip to content

Commit

Permalink
Refactor defineIdleProperties into IdleValue
Browse files Browse the repository at this point in the history
  • Loading branch information
philipwalton committed Aug 18, 2018
1 parent bbb00ee commit 8fde15d
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 181 deletions.
70 changes: 70 additions & 0 deletions lib/idle-value.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


import {cIC, rIC} from './utilities';

/**
* A class that wraps a value that is initialied when idle.
*/
export default class IdleValue {
/**
* Accepts a function to initialize the value of a variable when idle.
* @param {function():?} init
*/
constructor(init) {
this.init_ = init;

/** @type (?|undefined) */
this.value_;

this.idleHandle_ = rIC(() => {
this.value_ = this.init_();
});
}

/**
* Returns the value if it's already been initialized. If it hasn't then the
* initializer function is run immediately and the pending idle callback
* is cancelled.
* @return {?}
*/
get() {
if (this.value_ === undefined) {
this.cancleIdleInit_();
this.value_ = this.init_();
}
return this.value_;
}

/**
* @param {?} newValue
*/
set(newValue) {
this.cancleIdleInit_();
this.value_ = newValue;
}

/**
* Cancels any scheduled requestIdleCallback and resets the handle.
*/
cancleIdleInit_() {
if (this.idleHandle_) {
cIC(this.idleHandle_);
this.idleHandle_ = null;
}
}
}
45 changes: 1 addition & 44 deletions lib/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ export const queueMicrotask = (() => {
microtaskQueue.push(microtask);

// Trigger a mutation observer callback, which is a microtask.
node.data = ++i % 2;
node.data = String(++i % 2);
};
};
}
Expand Down Expand Up @@ -345,49 +345,6 @@ export const cIC = supportsRequestIdleCallback_ ?
cancelIdleCallback : cancelIdleCallbackShim;


/**
* Defines a getter and setter on on a property that idly runs and initializer.
* If the property is referred and the initializer has not yet run, it is
* run immediately and the value returned. The setter allows the value to be
* assign, and the property is configurable so a new idle property can be
* defined at any time.
* @param {!Object} obj
* @param {string} prop
* @param {!Function} init The initialization function whose return value
* is the initial value of the property.
*/
export const defineIdleProperty = (obj, prop, init) => {
let value;
const handle = rIC(() => {
value = init();
});
Object.defineProperty(obj, prop, {
configurable: true,
get: () => {
if (value === undefined) {
cIC(handle);
value = init();
}
return value;
},
set: (newValue) => {
value = newValue;
},
});
};

/**
* Invokes `defineIdleProperty` for each entry in the property object passed.
* @param {!Object} obj
* @param {!Object<string, !Function>} props
*/
export const defineIdleProperties = (obj, props) => {
Object.keys(props).forEach((prop) => {
const init = props[prop];
defineIdleProperty(obj, prop, init);
});
};

/*eslint-disable */
// https://gist.github.com/jed/982883
/** @param {?=} a */
Expand Down
135 changes: 135 additions & 0 deletions test/unit/idle-value-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/**
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import IdleValue from '../../lib/idle-value';
import {nextIdleCallback} from './helpers';


const sandbox = sinon.createSandbox();

describe('IdleValue', () => {
beforeEach(() => {
sandbox.restore();
});

afterEach(() => {
sandbox.restore();
});

describe('IdleValue', () => {
describe('constructor', () => {
it('initializes a value when idle', async () => {
const initStub = sandbox.stub().returns('42');
new IdleValue(initStub);

assert(initStub.notCalled);

await nextIdleCallback();

assert(initStub.calledOnce);
});
});

describe('get', () => {
it('returns the value immediately when already initialized', async () => {
const initStub = sandbox.stub().returns('42');
const idleVal = new IdleValue(initStub);

await nextIdleCallback();
assert(initStub.calledOnce);

const val = idleVal.get();

assert.strictEqual(val, '42');
});

it('runs the init function immediately if the value not yet set', () => {
const initStub = sandbox.stub().returns('42');
const idleVal = new IdleValue(initStub);

assert(initStub.notCalled);

const val = idleVal.get();
assert.strictEqual(val, '42');
assert(initStub.calledOnce);
});

it('cancels the idle request if run before idle', async () => {
const initStub = sandbox.stub().returns('42');
const idleVal = new IdleValue(initStub);

const val = idleVal.get();
assert(initStub.calledOnce);
assert.strictEqual(val, '42');

await nextIdleCallback();

// Assert the init function wasn't called again.
assert(initStub.calledOnce);
});

it('does not initialize the value more than once', async () => {
const initStub = sandbox.stub().returns('42');
const idleVal = new IdleValue(initStub);

let val = idleVal.get();
assert.strictEqual(val, '42');
assert(initStub.calledOnce);

val = idleVal.get();
assert.strictEqual(val, '42');
assert(initStub.calledOnce);

await nextIdleCallback();

val = idleVal.get();
assert.strictEqual(val, '42');
assert(initStub.calledOnce);
});
});

describe('set', () => {
it('updates the value', () => {
const initStub = sandbox.stub().returns('42');
const idleVal = new IdleValue(initStub);

let val = idleVal.get();
assert.strictEqual(val, '42');

idleVal.set('43');

val = idleVal.get();
assert.strictEqual(val, '43');
});

it('cancels the idle request if run before idle', async () => {
const initStub = sandbox.stub().returns('42');
const idleVal = new IdleValue(initStub);

idleVal.set('43');
assert(initStub.notCalled);

let val = idleVal.get();
assert.strictEqual(val, '43');
assert(initStub.notCalled);

await nextIdleCallback();

assert(initStub.notCalled);
});
});
});
});
Loading

0 comments on commit 8fde15d

Please sign in to comment.