Skip to content

Commit

Permalink
feat(util): add debounce example util
Browse files Browse the repository at this point in the history
  • Loading branch information
NullVoxPopuli committed Jun 11, 2022
1 parent 4f84f83 commit 357e266
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 0 deletions.
4 changes: 4 additions & 0 deletions ember-resources/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"./util/map": "./dist/util/map.js",
"./util/helper": "./dist/util/helper.js",
"./util/remote-data": "./dist/util/remote-data.js",
"./util/debounce": "./dist/util/debounce.js",
"./util/function": "./dist/util/function.js",
"./util/function-resource": "./dist/util/function-resource.js",
"./util/ember-concurrency": "./dist/util/ember-concurrency.js",
Expand Down Expand Up @@ -42,6 +43,9 @@
"util/helper": [
"dist/util/helper.d.ts"
],
"util/debounce": [
"dist/util/debounce.d.ts"
],
"util/remote-data": [
"dist/util/remote-data.d.ts"
],
Expand Down
72 changes: 72 additions & 0 deletions ember-resources/src/util/debounce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { tracked } from '@glimmer/tracking';

import { resource } from './function-resource';

class TrackedValue<T> {
@tracked value: T | undefined;
}

/**
* A utility for debouncing high-frequency updates.
* The returned value will only be updated every `ms` and is
* initially undefined.
*
* This can be useful when a user's typing is updating a tracked
* property and you want to derive data less frequently than on
* each keystroke.
*
* Note that this utility requires the @use decorator
* (debounce could be implemented without the need for the @use decorator
* but the current implementation is 8 lines)
*
* @example
* ```js
* import Component from '@glimmer/component';
* import { tracked } from '@glimmer/tracking';
* import { debounce } from 'ember-resources/util/debounce';
*
* const delay = 100; // ms
*
* class Demo extends Component {
* @tracked userInput = '';
*
* @use debouncedInput = debounce(delay, () => this.userInput);
* }
* ```
*
* @example
* This could be further composed with [[RemoteData]]
* ```js
* import Component from '@glimmer/component';
* import { tracked } from '@glimmer/tracking';
* import { debounce } from 'ember-resources/util/debounce';
* import { RemoteData } from 'ember-resources/util/remote-data';
*
* const delay = 100; // ms
*
* class Demo extends Component {
* @tracked userInput = '';
*
* @use debouncedInput = debounce(delay, () => this.userInput);
*
* @use search = RemoteData(() => `https://my.domain/search?q=${this.debouncedInput}`);
* }
* ```
*
* @param {number} ms delay in milliseconds to wait before updating the returned value
* @param {() => Value} thunk function that returns the value to debounce
*/
export function debounce<Value = unknown>(ms: number, thunk: () => Value) {
let lastValue: Value;
let timer: number;
let state = new TrackedValue();

return resource(({ on }) => {
lastValue = thunk();

on.cleanup(() => timer && clearTimeout(timer));
timer = setTimeout(() => (state.value = lastValue), ms);

return state.value;
});
}
48 changes: 48 additions & 0 deletions testing/ember-app/tests/utils/debounce/js-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { tracked } from '@glimmer/tracking';
import { setOwner } from '@ember/application';
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';

import { debounce } from 'ember-resources/util/debounce';
import { use } from 'ember-resources/util/function-resource';

module('Utils | debounce | js', function (hooks) {
setupTest(hooks);

let someTime = (ms = 25) => new Promise((resolve) => setTimeout(resolve, ms));

module('debounce', function () {
test('works with @use', async function (assert) {
class Test {
@tracked data = '';

@use text = debounce(100, () => this.data);
}

let test = new Test();

setOwner(test, this.owner);

assert.strictEqual(test.text, undefined);

test.data = 'b';
await someTime();
assert.strictEqual(test.text, undefined);
test.data = 'bo';
await someTime();
assert.strictEqual(test.text, undefined);
test.data = 'boo';
await someTime();
assert.strictEqual(test.text, undefined);

await someTime(110);
assert.strictEqual(test.text, 'boo');

test.data = 'boop';
assert.strictEqual(test.text, 'boo');

await someTime(110);
assert.strictEqual(test.text, 'boop');
});
});
});

0 comments on commit 357e266

Please sign in to comment.