diff --git a/README.md b/README.md index f6be6d0..2117d52 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,20 @@ # Portfolio -These are my personal projects. +These are my personal projects. This is where I come to refactor to my heart's content. -# Packages +# Projects + +## [Gallery](./packages/gallery/README.md) + +A browser-based app for viewing photos. (WIP) + +# Major Tools ## [FRP tools](./packages/frptools/README.md) Observable property and computed value stores designed to work together for storing real and derived state. -## [Gallery](./packages/gallery/README.md) - -A browser-based app for viewing photos. (WIP) - ## [Portal](./packages/portal/README.md) A utility to expose an asynchronous API between a web worker and its parent. @@ -26,6 +28,12 @@ An type-based abstraction layer over PouchDB inspired by [Hood.ie](https://hood. A slim and unopinionated hash router. +# Minor Tools + +## [Background Task](./packages/backgroundtask/README.md) + +A simple way to run a task on the main thread without disrupting the UX (much). + ## [Trimkit](./packages/trimkit/README.md) Javascript API abstractions to enhance minification by substituting variables. It's really quite diff --git a/packages/backgroundtask/README.md b/packages/backgroundtask/README.md new file mode 100644 index 0000000..4ab2b3d --- /dev/null +++ b/packages/backgroundtask/README.md @@ -0,0 +1,25 @@ +# Background task + +An simple function wrapper around +[requestIdleCallback](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback) +that queues tasks and runs them sequentially. + +The main purpose is to be able to run maintainance functions that are not a good fit for a web +worker context. + +# Usage + +Wrap your function. + +``` + const task = backgroundTask(() => { + // do stuff + }); +``` + +Call `task` as you would any other function that has no return value. The return value of a +background task is the id from `requestIdleCallback` that can be passed to `cancelIdleCallback` if +you wish to cancel the task before it runs. + +Calling a task multiple times will queue the passed parameters and execute the task sequentially the +same number of times. diff --git a/packages/backgroundtask/package.json b/packages/backgroundtask/package.json new file mode 100644 index 0000000..83c1e04 --- /dev/null +++ b/packages/backgroundtask/package.json @@ -0,0 +1,15 @@ +{ + "name": "backgroundtask", + "version": "1.0.0", + "description": "A simple way to run a task on the main thread without disrupting the UX", + "main": "src/index.js", + "files": [ + "src" + ], + "scripts": { + "test": "node ../../bin/runTests.js ./", + "pre-commit": "npm run test" + }, + "author": "Timothy Farrell (https://github.com/explorigin)", + "license": "Apache-2.0" +} diff --git a/packages/backgroundtask/spec/backgroundTask.spec.js b/packages/backgroundtask/spec/backgroundTask.spec.js new file mode 100644 index 0000000..0185962 --- /dev/null +++ b/packages/backgroundtask/spec/backgroundTask.spec.js @@ -0,0 +1,46 @@ +import { backgroundTask } from '../src/index.js'; + +describe('A background task', () => { + it('initially does nothing.', done => { + const task = backgroundTask(() => + done(new Error('Background tasks should not spontaneously execute.')) + ); + expect().nothing(); + setTimeout(done, 50); + }); + + it('runs a task (but not right away)', done => { + let shouldRun = false; + const task = backgroundTask(() => { + expect(shouldRun).toBeTruthy(); + done(); + }); + + task(); + shouldRun = true; + setTimeout(done, 50); + }); + + it('passes params', done => { + let testVal = Math.random(); + const task = backgroundTask(val => { + expect(val).toEqual(testVal); + done(); + }); + + task(testVal); + setTimeout(done, 50); + }); + + it('queues multiple calls to be run sequentially', done => { + let testVal = ['a', 'd', 'q']; + const task = backgroundTask((val, index) => { + expect(val).toEqual(testVal[index]); + if (index === testVal.length - 1) { + done(); + } + }); + + testVal.map(task); + }); +}); diff --git a/packages/backgroundtask/src/index.js b/packages/backgroundtask/src/index.js new file mode 100644 index 0000000..6055ecb --- /dev/null +++ b/packages/backgroundtask/src/index.js @@ -0,0 +1,46 @@ +// requestIdleCallback sortof-polyfill +if (!self.requestIdleCallback) { + const IDLE_TIMEOUT = 10; + self.requestIdleCallback = cb => { + let start = Date.now(); + return setTimeout( + () => + cb({ + timeRemaining: () => Math.max(0, IDLE_TIMEOUT - (Date.now() - start)) + }), + 1 + ); + }; + self.requestIdleCallback = clearTimeout; +} + +export function backgroundTask(fn, timeout = undefined) { + let id = null; + const params = []; + + async function runTask({ didTimeout }) { + if (didTimeout) { + id = requestIdleCallback(runTask); + return; + } + const start = Date.now(); + const p = params.shift(); + await fn(...p); + if (params.length) { + id = requestIdleCallback(runTask, { timeout }); + } else { + id = null; + } + } + + const wrapper = (...args) => { + params.push(args); + if (id !== null) { + return false; + } + id = requestIdleCallback(runTask, { timeout }); + return true; + }; + + return wrapper; +} diff --git a/packages/gallery/package.json b/packages/gallery/package.json index 84ee163..627f6b4 100644 --- a/packages/gallery/package.json +++ b/packages/gallery/package.json @@ -12,6 +12,7 @@ "dev": "webpack-dev-server" }, "dependencies": { + "backgroundtask": "~1.0.0", "body-parser": "~1.18.3", "date-fns": "~1.29.0", "domvm": "~3.2.1", diff --git a/packages/gallery/src/data/image.js b/packages/gallery/src/data/image.js index 3316ad1..f63d5ed 100644 --- a/packages/gallery/src/data/image.js +++ b/packages/gallery/src/data/image.js @@ -1,8 +1,8 @@ import { TypeHandler } from 'pouchtype'; +import { backgroundTask } from 'backgroundtask'; import { db } from '../services/db.js'; import { blobToArrayBuffer, deepAssign } from '../utils/conversion.js'; -import { backgroundTask } from '../utils/event.js'; import { FileType } from './file.js'; import { error } from '../utils/console.js'; diff --git a/packages/gallery/src/utils/attachmentProxy.js b/packages/gallery/src/utils/attachmentProxy.js index 7ad64b3..71d29ba 100644 --- a/packages/gallery/src/utils/attachmentProxy.js +++ b/packages/gallery/src/utils/attachmentProxy.js @@ -1,5 +1,6 @@ import core from 'pouchdb-core'; -import { backgroundTask } from '../utils/event.js'; +import { backgroundTask } from 'backgroundtask'; + import { deepAssign, blobToObj } from '../utils/conversion.js'; import { error, log } from '../utils/console.js'; diff --git a/packages/gallery/src/utils/event.js b/packages/gallery/src/utils/event.js index d145e3d..addb245 100644 --- a/packages/gallery/src/utils/event.js +++ b/packages/gallery/src/utils/event.js @@ -1,62 +1,5 @@ import { log, group, groupEnd } from '../utils/console.js'; -// requestIdleCallback sortof-polyfill -if (!global.requestIdleCallback) { - const IDLE_TIMEOUT = 10; - global.requestIdleCallback = cb => { - let start = Date.now(); - return setTimeout( - () => - cb({ - timeRemaining: () => Math.max(0, IDLE_TIMEOUT - (Date.now() - start)) - }), - 1 - ); - }; -} - -export function backgroundTask(fn, initialDelay = 500) { - let id = null; - const params = []; - - async function runTask({ didTimeout }) { - if (didTimeout) { - id = requestIdleCallback(runTask); - return; - } - const start = Date.now(); - group(fn.name); - const p = params.shift(); - if (p.length) { - log(`${fn.name} params: `, ...p); - } - await fn(...p); - const executionTime = Date.now() - start; - log(`${fn.name} execution time: ${executionTime}ms`); - groupEnd(fn.name); - if (params.length) { - id = requestIdleCallback(runTask); - } else { - id = null; - } - } - - const wrapper = (...args) => { - params.push(args); - if (id !== null) { - return false; - } - id = requestIdleCallback(runTask); - return true; - }; - - if (initialDelay) { - setTimeout(wrapper, initialDelay); - } - - return wrapper; -} - export const streamConfig = { is: s => s && typeof s.subscribe === 'function', val: s => s(),