├── dist ├── backburner │ ├── binary-search.d.ts │ ├── interfaces.d.ts │ ├── iterator-drain.d.ts │ ├── platform.d.ts │ ├── utils.d.ts │ ├── queue.d.ts │ └── deferred-action-queues.d.ts ├── tests │ ├── index.html │ └── qunit.css ├── bench │ └── index.html ├── loader.js └── backburner.d.ts ├── .gitignore ├── server └── index.js ├── lib └── backburner │ ├── interfaces.ts │ ├── iterator-drain.ts │ ├── binary-search.ts │ ├── platform.ts │ ├── utils.ts │ ├── deferred-action-queues.ts │ └── queue.ts ├── .npmignore ├── .vscode ├── settings.json ├── tasks.json └── launch.json ├── .editorconfig ├── tsconfig.json ├── .github └── workflows │ └── ci.yml ├── testem.js ├── tests ├── index.html ├── utils │ └── mock-stable-error.ts ├── index.ts ├── build-next-test.ts ├── bb-has-timers-test.ts ├── defer-iterable-test.ts ├── defer-debug-info-test.ts ├── debug-test.ts ├── async-stack-test.ts ├── queue-test.ts ├── queue-debug-info-test.ts ├── multi-turn-test.ts ├── configurable-timeout-test.ts ├── events-test.ts ├── run-test.ts ├── join-test.ts ├── debug-info-test.ts ├── defer-test.ts ├── cancel-test.ts ├── autorun-test.ts ├── defer-once-test.ts ├── queue-push-unique-test.ts ├── later-test.ts └── throttle-test.ts ├── tslint.json ├── bench ├── index.html ├── index.js ├── benches │ ├── later-cancel.js │ ├── debounce-cancel.js │ ├── throttle-cancel.js │ ├── schedule-flush.js │ └── schedule-cancel.js └── browser-bench.js ├── CHANGELOG.md ├── LICENSE ├── package.json ├── RELEASE.md ├── README.md └── ember-cli-build.js /dist/backburner/binary-search.d.ts: -------------------------------------------------------------------------------- 1 | export default function binarySearch(time: any, timers: any): number; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | bower_components 4 | tmp/ 5 | dist/ 6 | npm-debug.log 7 | .vscode/chrome 8 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function(app) { 2 | app.get('/', function(req, res) { 3 | res.redirect('/tests/?hidepassed'); 4 | }) 5 | }; 6 | -------------------------------------------------------------------------------- /lib/backburner/interfaces.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface IQueueItem { 3 | method: string; 4 | target: Object; 5 | args: Object[]; 6 | stack: string | undefined; 7 | } 8 | -------------------------------------------------------------------------------- /dist/backburner/interfaces.d.ts: -------------------------------------------------------------------------------- 1 | export interface IQueueItem { 2 | method: string; 3 | target: Object; 4 | args: Object[]; 5 | stack: string | undefined; 6 | } 7 | -------------------------------------------------------------------------------- /dist/backburner/iterator-drain.d.ts: -------------------------------------------------------------------------------- 1 | export interface Iterable { 2 | next: () => { 3 | done: boolean; 4 | value?: any; 5 | }; 6 | } 7 | export default function (fn: () => Iterable): void; 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | bower_components/ 4 | /tmp/ 5 | ember-cli-build.js 6 | testem.json 7 | TODOs 8 | /bin/ 9 | /config/ 10 | /lib/ 11 | tests/ 12 | .travis.yml 13 | .npmignore 14 | .gitignore 15 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.git": true, 4 | "**/.DS_Store": true, 5 | ".vscode/chrome": true, 6 | "tmp": true, 7 | "config": true 8 | }, 9 | "editor.insertSpaces": true, 10 | "editor.tabSize": 2 11 | } 12 | -------------------------------------------------------------------------------- /dist/backburner/platform.d.ts: -------------------------------------------------------------------------------- 1 | export interface IPlatform { 2 | setTimeout(fn: Function, ms: number): any; 3 | clearTimeout(id: any): void; 4 | next(): any; 5 | clearNext(): void; 6 | now(): number; 7 | } 8 | export declare function buildNext(flush: () => void): () => void; 9 | export declare function buildPlatform(flush: () => void): IPlatform; 10 | -------------------------------------------------------------------------------- /lib/backburner/iterator-drain.ts: -------------------------------------------------------------------------------- 1 | // accepts a function that when invoked will return an iterator 2 | // iterator will drain until completion 3 | export interface Iterable { 4 | next: () => { done: boolean, value?: any }; 5 | } 6 | 7 | export default function(fn: () => Iterable) { 8 | let iterator = fn(); 9 | let result = iterator.next(); 10 | 11 | while (result.done === false) { 12 | result.value(); 13 | result = iterator.next(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.js] 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [*.{diff,md}] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "target": "es2015", 5 | "noEmit": true, 6 | "baseUrl": ".", 7 | "inlineSourceMap": true, 8 | "inlineSources": true, 9 | "module": "es2015", 10 | "strictNullChecks": true, 11 | "declaration": true, 12 | "paths": { 13 | "backburner": ["lib/index.ts"] 14 | } 15 | }, 16 | "files": [ 17 | "lib/index.ts", 18 | "tests/index.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: actions/setup-node@v2 17 | with: 18 | node-version: 14 19 | - run: yarn install --frozen-lockfile 20 | - run: yarn problems 21 | - run: yarn lint 22 | - run: yarn test 23 | - run: yarn bench 24 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "0.1.0", 5 | "command": "npm", 6 | "isShellCommand": true, 7 | "suppressTaskName": true, 8 | "tasks": [ 9 | { 10 | "taskName": "build", 11 | "args": ["run", "build"], 12 | "isBuildCommand": true 13 | }, { 14 | "taskName": "serve", 15 | "args": ["run", "serve"], 16 | "isBackground": true 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /dist/backburner/utils.d.ts: -------------------------------------------------------------------------------- 1 | import { IQueueItem } from './interfaces'; 2 | export declare const QUEUE_ITEM_LENGTH = 5; 3 | export declare const TIMERS_OFFSET = 7; 4 | export declare function isCoercableNumber(suspect: any): boolean; 5 | export declare function getOnError(options: any): any; 6 | export declare function findItem(target: any, method: any, collection: any): number; 7 | export declare function findTimerItem(target: any, method: any, collection: any): number; 8 | export declare function getQueueItems(items: any[], queueItemLength: number, queueItemPositionOffset?: number): IQueueItem[]; 9 | -------------------------------------------------------------------------------- /testem.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | framework: 'qunit', 3 | test_page: 'tests/index.html', 4 | launch_in_ci: ['Chrome'], 5 | launch_in_dev: ['Chrome'], 6 | browser_args: { 7 | Chrome: { 8 | ci: [ 9 | // --no-sandbox is needed when running Chrome inside a container 10 | process.env.CI ? '--no-sandbox' : null, 11 | '--headless', 12 | '--disable-gpu', 13 | '--disable-dev-shm-usage', 14 | '--disable-software-rasterizer', 15 | '--mute-audio', 16 | '--remote-debugging-port=0', 17 | '--window-size=1440,900' 18 | ].filter(Boolean) 19 | } 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Backburner.js Tests 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /dist/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Backburner.js Tests 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/utils/mock-stable-error.ts: -------------------------------------------------------------------------------- 1 | 2 | const ERROR = Error; 3 | // @ts-ignore - Skip preventing overriding the readonly Error object 4 | Error = ERROR; 5 | let stacks: string[] = []; 6 | 7 | export function pushStackTrace(stackLine: string) { 8 | stacks.push(stackLine); 9 | 10 | return stackLine; 11 | } 12 | 13 | export function overrideError(_Error) { 14 | // @ts-ignore 15 | Error = _Error; 16 | } 17 | 18 | export function resetError() { 19 | // @ts-ignore 20 | Error = ERROR; 21 | stacks = []; 22 | } 23 | 24 | export default class MockStableError { 25 | constructor(public message: string) {} 26 | 27 | get stack(): string { 28 | return stacks.pop() || ''; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/index.ts: -------------------------------------------------------------------------------- 1 | import './async-stack-test'; 2 | import './autorun-test'; 3 | import './bb-has-timers-test'; 4 | import './build-next-test'; 5 | import './cancel-test'; 6 | import './configurable-timeout-test'; 7 | import './debounce-test'; 8 | import './debug-info-test'; 9 | import './debug-test'; 10 | import './defer-debug-info-test'; 11 | import './defer-iterable-test'; 12 | import './defer-once-test'; 13 | import './defer-test'; 14 | import './events-test'; 15 | import './join-test'; 16 | import './later-test'; 17 | import './multi-turn-test'; 18 | import './queue-debug-info-test'; 19 | import './queue-push-unique-test'; 20 | import './queue-test'; 21 | import './run-test'; 22 | import './throttle-test'; 23 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "quotemark": [true, "single", "avoid-escape"], 5 | "trailing-comma": [false], 6 | "object-literal-shorthand": false, 7 | "object-literal-sort-keys": false, 8 | "only-arrow-functions": [false], 9 | "jsdoc-format": false, 10 | "prefer-for-of": false, 11 | "no-empty": false, 12 | "no-string-literal": false, 13 | "prefer-const": false, 14 | "max-line-length": [false], 15 | "variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore"], 16 | "ban-types": {} 17 | }, 18 | "jsRules": { 19 | "quotemark": [true, "single", "avoid-escape"], 20 | "trailing-comma": [false] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/backburner/binary-search.ts: -------------------------------------------------------------------------------- 1 | import { TIMERS_OFFSET } from './utils'; 2 | 3 | export default function binarySearch(time, timers) { 4 | let start = 0; 5 | let end = timers.length - TIMERS_OFFSET; 6 | let middle; 7 | let l; 8 | 9 | while (start < end) { 10 | // since timers is an array of pairs 'l' will always 11 | // be an integer 12 | l = (end - start) / TIMERS_OFFSET; 13 | 14 | // compensate for the index in case even number 15 | // of pairs inside timers 16 | middle = start + l - (l % TIMERS_OFFSET); 17 | 18 | if (time >= timers[middle]) { 19 | start = middle + TIMERS_OFFSET; 20 | } else { 21 | end = middle; 22 | } 23 | } 24 | 25 | return (time >= timers[start]) ? start + TIMERS_OFFSET : start; 26 | } 27 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible Node.js debug attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch localhost", 9 | "type": "chrome", 10 | "request": "launch", 11 | "url": "http://localhost:4200/tests/", 12 | "userDataDir": "${workspaceRoot}/.vscode/chrome", 13 | "sourceMaps": true, 14 | "webRoot": "${workspaceRoot}/dist", 15 | "sourceMapPathOverrides": { 16 | "../lib/*.ts": "${workspaceRoot}/lib/*.ts", 17 | "../tests/*.ts": "${workspaceRoot}/tests/*.ts" 18 | } 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /bench/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Backburner.js Benchmarks 6 | 7 | 8 |
9 | 10 | 11 | 12 |
13 |
14 |   
15 | 16 | 17 | 18 | 19 | 20 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /dist/bench/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Backburner.js Benchmarks 6 | 7 | 8 |
9 | 10 | 11 | 12 |
13 |
14 |   
15 | 16 | 17 | 18 | 19 | 20 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/build-next-test.ts: -------------------------------------------------------------------------------- 1 | import Backburner from 'backburner'; 2 | 3 | QUnit.module('tests/build-next', function() { 4 | 5 | QUnit.test('can build custom flushing next', function(assert) { 6 | let done = assert.async(); 7 | let next = Backburner.buildNext(() => assert.step('custom next')); 8 | 9 | assert.step('start'); 10 | Promise.resolve().then(() => assert.step('first promise resolved')); 11 | next(); 12 | Promise.resolve().then(() => assert.step('second promise resolved')); 13 | assert.step('end'); 14 | 15 | setTimeout(() => { 16 | assert.verifySteps([ 17 | 'start', 18 | 'end', 19 | 'first promise resolved', 20 | 'custom next', 21 | 'second promise resolved', 22 | ]); 23 | done(); 24 | }, 10); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /bench/index.js: -------------------------------------------------------------------------------- 1 | var glob = require('glob'); 2 | var path = require('path'); 3 | var bench = require('do-you-even-bench'); 4 | 5 | // to run all (default): 6 | // 7 | // node ./bench/index.js 8 | // 9 | // to run a single benchmark, you can do: 10 | // 11 | // node ./bench/index.js ./bench/benches/some-file.js 12 | var fileGlob = './bench/benches/*.js'; 13 | if (process.argv[2]) { 14 | fileGlob = process.argv[2]; 15 | console.log(fileGlob); 16 | } 17 | 18 | globalThis.Backburner = require("../dist/backburner").default; 19 | 20 | var suites = []; 21 | glob.sync(fileGlob).forEach(function(file) { 22 | var exported = require( path.resolve( file ) ); 23 | if (Array.isArray(exported)) { 24 | suites = suites.concat(exported); 25 | } else { 26 | suites.push(exported); 27 | } 28 | }); 29 | 30 | bench(suites); 31 | -------------------------------------------------------------------------------- /bench/benches/later-cancel.js: -------------------------------------------------------------------------------- 1 | function sharedSetup() { 2 | var backburner = new this.Backburner(["sync", "actions", "routerTransitions", "render", "afterRender", "destroy", "rsvpAfter"]); 3 | 4 | var target = { 5 | someMethod: function() { } 6 | }; 7 | } 8 | 9 | module.exports = [ 10 | { 11 | name: 'Later & Cancel - function', 12 | 13 | Backburner: Backburner, 14 | 15 | setup: sharedSetup, 16 | 17 | fn: function() { 18 | var timer = backburner.later(null, target.someMethod, 100); 19 | backburner.cancel(timer); 20 | } 21 | }, 22 | { 23 | name: 'Later & Cancel - function, target', 24 | 25 | Backburner: Backburner, 26 | 27 | setup: sharedSetup, 28 | 29 | fn: function() { 30 | var timer = backburner.later(target, 'someMethod', 100); 31 | backburner.cancel(timer); 32 | } 33 | } 34 | ]; 35 | -------------------------------------------------------------------------------- /bench/browser-bench.js: -------------------------------------------------------------------------------- 1 | import bench from "do-you-even-bench"; 2 | import Benchmark from "benchmark"; 3 | window.Benchmark = Benchmark; 4 | 5 | import DebounceCancel from "./benches/debounce-cancel"; 6 | import LaterCancel from "./benches/later-cancel"; 7 | import ScheduleCancel from "./benches/schedule-cancel"; 8 | import ScheduleFlush from "./benches/schedule-flush"; 9 | import ThrottleCancel from "./benches/throttle-cancel"; 10 | 11 | let suites = [ 12 | DebounceCancel, 13 | LaterCancel, 14 | ScheduleCancel, 15 | ScheduleFlush, 16 | ThrottleCancel, 17 | ].flat(); 18 | 19 | const searchParams = new URLSearchParams(window.location.search); 20 | const filter = searchParams.get("filter"); 21 | 22 | if (filter) { 23 | document.querySelector("input[name=filter]").value = filter; 24 | suites = suites.filter((s) => 25 | s.name.toLowerCase().includes(filter.toLowerCase()) 26 | ); 27 | } 28 | 29 | bench(suites); 30 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v2.7.0 (2021-04-30) 2 | 3 | #### :rocket: Enhancement 4 | * [#383](https://github.com/BackburnerJS/backburner.js/pull/383) Export types ([@wagenet](https://github.com/wagenet)) 5 | 6 | #### :memo: Documentation 7 | * [#390](https://github.com/BackburnerJS/backburner.js/pull/390) Remove Downloads section from README ([@rwjblue](https://github.com/rwjblue)) 8 | 9 | #### :house: Internal 10 | * [#391](https://github.com/BackburnerJS/backburner.js/pull/391) Add basic release automation setup. ([@rwjblue](https://github.com/rwjblue)) 11 | * [#388](https://github.com/BackburnerJS/backburner.js/pull/388) Re-roll yarn.lock ([@rwjblue](https://github.com/rwjblue)) 12 | * [#382](https://github.com/BackburnerJS/backburner.js/pull/382) Upgrade development dependencies ([@wagenet](https://github.com/wagenet)) 13 | 14 | #### Committers: 3 15 | - Cyrille David ([@dcyriller](https://github.com/dcyriller)) 16 | - Peter Wagenet ([@wagenet](https://github.com/wagenet)) 17 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue)) 18 | 19 | 20 | # Changelog 21 | -------------------------------------------------------------------------------- /tests/bb-has-timers-test.ts: -------------------------------------------------------------------------------- 1 | import Backburner from 'backburner'; 2 | 3 | QUnit.module('tests/bb-has-timers'); 4 | 5 | QUnit.test('hasTimers', function(assert) { 6 | let done = assert.async(); 7 | let bb = new Backburner(['ohai']); 8 | let timer; 9 | let target = { 10 | fn() { } 11 | }; 12 | 13 | bb.schedule('ohai', null, () => { 14 | assert.ok(!bb.hasTimers(), 'Initially there are no timers'); 15 | 16 | timer = bb.later('ohai', () => {}); 17 | assert.ok(bb.hasTimers(), 'hasTimers checks timers'); 18 | 19 | bb.cancel(timer); 20 | 21 | assert.ok(!bb.hasTimers(), 'Timers are cleared'); 22 | 23 | timer = bb.debounce(target, 'fn', 200); 24 | assert.ok(bb.hasTimers(), 'hasTimers checks debouncees'); 25 | 26 | bb.cancel(timer); 27 | assert.ok(!bb.hasTimers(), 'Timers are cleared'); 28 | 29 | timer = bb.throttle(target, 'fn', 200); 30 | assert.ok(bb.hasTimers(), 'hasTimers checks throttlers'); 31 | 32 | bb.cancel(timer); 33 | assert.ok(!bb.hasTimers(), 'Timers are cleared'); 34 | 35 | done(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Erik Bryn and contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /dist/backburner/queue.d.ts: -------------------------------------------------------------------------------- 1 | import { IQueueItem } from './interfaces'; 2 | export declare const enum QUEUE_STATE { 3 | Pause = 1 4 | } 5 | export default class Queue { 6 | private name; 7 | private globalOptions; 8 | private options; 9 | private _queueBeingFlushed; 10 | private targetQueues; 11 | private index; 12 | private _queue; 13 | constructor(name: string, options?: any, globalOptions?: any); 14 | stackFor(index: any): any; 15 | consoleTaskFor(index: any, inQueueBeingFlushed?: boolean): any; 16 | flush(sync?: Boolean): QUEUE_STATE.Pause | undefined; 17 | hasWork(): boolean; 18 | cancel({ target, method }: { 19 | target: any; 20 | method: any; 21 | }): boolean; 22 | push(target: any, method: any, args: any, stack: any, consoleTask: any): { 23 | queue: Queue; 24 | target: any; 25 | method: any; 26 | }; 27 | pushUnique(target: any, method: any, args: any, stack: any, consoleTask: any): { 28 | queue: Queue; 29 | target: any; 30 | method: any; 31 | }; 32 | _getDebugInfo(debugEnabled: boolean): IQueueItem[] | undefined; 33 | private invoke; 34 | private invokeWithOnError; 35 | } 36 | -------------------------------------------------------------------------------- /dist/backburner/deferred-action-queues.d.ts: -------------------------------------------------------------------------------- 1 | import { IQueueItem } from './interfaces'; 2 | import Queue, { QUEUE_STATE } from './queue'; 3 | export interface IDebugInfo { 4 | [key: string]: IQueueItem[] | undefined; 5 | } 6 | export default class DeferredActionQueues { 7 | queues: { 8 | [name: string]: Queue; 9 | }; 10 | queueNameIndex: number; 11 | private queueNames; 12 | constructor(queueNames: string[] | undefined, options: any); 13 | /** 14 | * @method schedule 15 | * @param {String} queueName 16 | * @param {Any} target 17 | * @param {Any} method 18 | * @param {Any} args 19 | * @param {Boolean} onceFlag 20 | * @param {Any} stack 21 | * @return queue 22 | */ 23 | schedule(queueName: string, target: any, method: any, args: any, onceFlag: boolean, stack: any, consoleTask: any): { 24 | queue: Queue; 25 | target: any; 26 | method: any; 27 | }; 28 | /** 29 | * DeferredActionQueues.flush() calls Queue.flush() 30 | * 31 | * @method flush 32 | * @param {Boolean} fromAutorun 33 | */ 34 | flush(fromAutorun?: boolean): QUEUE_STATE.Pause | undefined; 35 | /** 36 | * Returns debug information for the current queues. 37 | * 38 | * @method _getDebugInfo 39 | * @param {Boolean} debugEnabled 40 | * @returns {IDebugInfo | undefined} 41 | */ 42 | _getDebugInfo(debugEnabled: boolean): IDebugInfo | undefined; 43 | } 44 | -------------------------------------------------------------------------------- /bench/benches/debounce-cancel.js: -------------------------------------------------------------------------------- 1 | function sharedSetup() { 2 | var backburner = new this.Backburner(["sync", "actions", "routerTransitions", "render", "afterRender", "destroy", "rsvpAfter"]); 3 | 4 | var target = { 5 | someMethod: function() { }, 6 | anotherMethod: function() { } 7 | }; 8 | 9 | var timer1 = null; 10 | var timer2 = null; 11 | } 12 | 13 | module.exports = [ 14 | { 15 | name: 'Debounce - function', 16 | 17 | Backburner: Backburner, 18 | 19 | setup: sharedSetup, 20 | 21 | fn: function() { 22 | backburner.debounce(target.someMethod, 50); 23 | backburner.debounce(target.anotherMethod, 100); 24 | timer1 = backburner.debounce(target.someMethod, 50); 25 | timer2 = backburner.debounce(target.anotherMethod, 100); 26 | }, 27 | 28 | teardown: function() { 29 | backburner.cancel(timer1); 30 | backburner.cancel(timer2); 31 | } 32 | }, 33 | { 34 | name: 'Debounce & Cancel - function, target', 35 | 36 | Backburner: Backburner, 37 | 38 | setup: sharedSetup, 39 | 40 | fn: function() { 41 | backburner.debounce(target, 'someMethod', 50); 42 | backburner.debounce(target, 'anotherMethod', 100); 43 | timer1 = backburner.debounce(target, 'someMethod', 50); 44 | timer2 = backburner.debounce(target, 'anotherMethod', 100); 45 | 46 | backburner.cancel(timer1); 47 | backburner.cancel(timer2); 48 | }, 49 | 50 | teardown: function() { 51 | timer1 = null 52 | timer2 = null 53 | } 54 | } 55 | ]; 56 | -------------------------------------------------------------------------------- /bench/benches/throttle-cancel.js: -------------------------------------------------------------------------------- 1 | function sharedSetup() { 2 | var backburner = new this.Backburner(["sync", "actions", "routerTransitions", "render", "afterRender", "destroy", "rsvpAfter"]); 3 | 4 | var target = { 5 | someMethod: function() { }, 6 | anotherMethod: function() { } 7 | }; 8 | 9 | var timer1 = null; 10 | var timer2 = null; 11 | } 12 | 13 | module.exports = [ 14 | { 15 | name: 'Throttle - function', 16 | 17 | Backburner: Backburner, 18 | 19 | setup: sharedSetup, 20 | 21 | fn: function() { 22 | backburner.throttle(target.someMethod, 50); 23 | backburner.throttle(target.anotherMethod, 100); 24 | timer1 = backburner.throttle(target.someMethod, 50); 25 | timer2 = backburner.throttle(target.anotherMethod, 100); 26 | }, 27 | 28 | teardown: function() { 29 | backburner.cancel(timer1); 30 | backburner.cancel(timer2); 31 | timer1 = null 32 | timer2 = null 33 | } 34 | }, 35 | { 36 | name: 'Throttle & Cancel - function, target', 37 | 38 | Backburner: Backburner, 39 | 40 | setup: sharedSetup, 41 | 42 | fn: function() { 43 | backburner.throttle(target, 'someMethod', 50); 44 | backburner.throttle(target, 'anotherMethod', 100); 45 | timer1 = backburner.throttle(target, 'someMethod', 50); 46 | timer2 = backburner.throttle(target, 'anotherMethod', 100); 47 | 48 | backburner.cancel(timer1); 49 | backburner.cancel(timer2); 50 | }, 51 | 52 | teardown: function() { 53 | timer1 = null 54 | timer2 = null 55 | } 56 | } 57 | ]; 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@discourse/backburner.js", 3 | "version": "2.7.1-0", 4 | "description": "A fork of backburner.js", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/discourse/backburner.js.git" 8 | }, 9 | "license": "MIT", 10 | "author": "Erik Bryn", 11 | "main": "dist/backburner.js", 12 | "jsnext:main": "dist/backburner.js", 13 | "types": "dist/backburner.d.ts", 14 | "scripts": { 15 | "bench": "ember build && node ./bench/index.js", 16 | "build": "ember build --environment=production", 17 | "lint": "tslint --project tsconfig.json", 18 | "problems": "tsc -p tsconfig.json --noEmit", 19 | "prepare": "npm run build", 20 | "serve": "ember serve", 21 | "test": "ember test", 22 | "test:server": "ember test --server" 23 | }, 24 | "devDependencies": { 25 | "@types/qunit": "^2.11.1", 26 | "broccoli-funnel": "^3.0.4", 27 | "broccoli-merge-trees": "^4.2.0", 28 | "broccoli-rollup": "^4.1.1", 29 | "broccoli-typescript-compiler": "^7.0.0", 30 | "do-you-even-bench": "^1.0.5", 31 | "ember-cli": "^3.26.1", 32 | "ember-cli-dependency-checker": "^3.2.0", 33 | "ember-cli-inject-live-reload": "2.0.2", 34 | "glob": "^7.1.6", 35 | "loader.js": "^4.7.0", 36 | "lodash": "^4.17.21", 37 | "lolex": "^6.0.0", 38 | "qunit": "^2.14.1", 39 | "release-it": "^14.2.1", 40 | "release-it-lerna-changelog": "^3.1.0", 41 | "rollup-plugin-buble": "^0.19.8", 42 | "rollup-plugin-commonjs": "^10.1.0", 43 | "rollup-plugin-node-resolve": "^5.2.0", 44 | "tslint": "^6.1.3" 45 | }, 46 | "publishConfig": { 47 | "registry": "https://registry.npmjs.org", 48 | "access": "public" 49 | }, 50 | "release-it": {} 51 | } 52 | -------------------------------------------------------------------------------- /tests/defer-iterable-test.ts: -------------------------------------------------------------------------------- 1 | import Backburner from 'backburner'; 2 | 3 | QUnit.module('tests/defer-iterable'); 4 | 5 | class Iterator { 6 | private _collection: Function[]; 7 | private _iteration: number = 0; 8 | constructor(collection: Function[]) { 9 | this._collection = collection; 10 | } 11 | 12 | public next() { 13 | let iteration = this._iteration++; 14 | let collection = this._collection; 15 | let done = collection.length <= iteration; 16 | 17 | let value = done ? undefined : collection[iteration]; 18 | 19 | return { 20 | done, 21 | value 22 | }; 23 | } 24 | } 25 | 26 | QUnit.test('deferIterable', function(assert) { 27 | let bb = new Backburner(['zomg']); 28 | let order = 0; 29 | 30 | let tasks = { 31 | one: { count: 0, order: -1 }, 32 | two: { count: 0, order: -1 }, 33 | three: { count: 0, order: -1 } 34 | }; 35 | 36 | function task1() { 37 | tasks.one.count++; 38 | tasks.one.order = order++; 39 | } 40 | 41 | function task2() { 42 | tasks.two.count++; 43 | tasks.two.order = order++; 44 | } 45 | 46 | function task3() { 47 | tasks.three.count++; 48 | tasks.three.order = order++; 49 | } 50 | 51 | let iterator = () => new Iterator([ 52 | task1, 53 | task2, 54 | task3 55 | ]); 56 | 57 | bb.run(() => { 58 | bb.scheduleIterable('zomg', iterator); 59 | 60 | assert.deepEqual(tasks, { 61 | one: { count: 0, order: -1 }, 62 | two: { count: 0, order: -1 }, 63 | three: { count: 0, order: -1 } 64 | }); 65 | }); 66 | 67 | assert.deepEqual(tasks, { 68 | one: { count: 1, order: 0 }, 69 | two: { count: 1, order: 1 }, 70 | three: { count: 1, order: 2 } 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /tests/defer-debug-info-test.ts: -------------------------------------------------------------------------------- 1 | import Backburner from 'backburner'; 2 | import MockStableError, { 3 | overrideError, 4 | pushStackTrace, 5 | resetError, 6 | } from './utils/mock-stable-error'; 7 | 8 | QUnit.module('tests/defer-debug-info', { 9 | beforeEach: function() { 10 | // @ts-ignore 11 | overrideError(MockStableError); 12 | }, 13 | 14 | afterEach: function() { 15 | resetError(); 16 | } 17 | }); 18 | 19 | QUnit.test('_getDebugInfo returns empty object with DEBUG = false', function(assert) { 20 | assert.expect(1); 21 | 22 | let debugInfo; 23 | let bb = new Backburner(['render', 'afterRender']); 24 | 25 | bb.run(() => { 26 | debugInfo = bb.currentInstance && bb.currentInstance._getDebugInfo(bb.DEBUG); 27 | 28 | assert.equal(debugInfo, undefined); 29 | }); 30 | }); 31 | 32 | QUnit.test('_getDebugInfo returns debugInfo when DEBUG = true', function(assert) { 33 | assert.expect(1); 34 | 35 | let debugInfo; 36 | let method = () => {}; 37 | let afterRenderStack = pushStackTrace('afterRender stack'); 38 | let renderStack = pushStackTrace('render stack'); 39 | let bb = new Backburner(['render', 'afterRender']); 40 | 41 | bb.DEBUG = true; 42 | 43 | bb.run(() => { 44 | bb.schedule('render', method); 45 | bb.schedule('afterRender', method); 46 | 47 | debugInfo = bb.currentInstance && bb.currentInstance._getDebugInfo(bb.DEBUG); 48 | 49 | assert.deepEqual(debugInfo, { 50 | render: [ 51 | { 52 | target: null, 53 | method, 54 | args: undefined, 55 | stack: renderStack 56 | } 57 | ], 58 | afterRender: [ 59 | { 60 | target: null, 61 | method, 62 | args: undefined, 63 | stack: afterRenderStack 64 | } 65 | ] 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /lib/backburner/platform.ts: -------------------------------------------------------------------------------- 1 | export interface IPlatform { 2 | setTimeout(fn: Function, ms: number): any; 3 | clearTimeout(id: any): void; 4 | next(): any; 5 | clearNext(): void; 6 | now(): number; 7 | } 8 | 9 | const SET_TIMEOUT = setTimeout; 10 | const NOOP = () => {}; 11 | 12 | export function buildNext(flush: () => void): () => void { 13 | // Using "promises first" here to: 14 | // 15 | // 1) Ensure more consistent experience on browsers that 16 | // have differently queued microtasks (separate queues for 17 | // MutationObserver vs Promises). 18 | // 2) Ensure better debugging experiences (it shows up in Chrome 19 | // call stack as "Promise.then (async)") which is more consistent 20 | // with user expectations 21 | // 22 | // When Promise is unavailable use MutationObserver (mostly so that we 23 | // still get microtasks on IE11), and when neither MutationObserver and 24 | // Promise are present use a plain old setTimeout. 25 | if (typeof Promise === 'function') { 26 | const autorunPromise = Promise.resolve(); 27 | 28 | return () => autorunPromise.then(flush); 29 | } else if (typeof MutationObserver === 'function') { 30 | let iterations = 0; 31 | let observer = new MutationObserver(flush); 32 | let node = document.createTextNode(''); 33 | observer.observe(node, { characterData: true }); 34 | 35 | return () => { 36 | iterations = ++iterations % 2; 37 | node.data = '' + iterations; 38 | return iterations; 39 | }; 40 | } else { 41 | return () => SET_TIMEOUT(flush, 0); 42 | } 43 | } 44 | 45 | export function buildPlatform(flush: () => void): IPlatform { 46 | let clearNext = NOOP; 47 | 48 | return { 49 | setTimeout(fn, ms) { 50 | return setTimeout(fn, ms); 51 | }, 52 | 53 | clearTimeout(timerId: number) { 54 | return clearTimeout(timerId); 55 | }, 56 | 57 | now() { 58 | return Date.now(); 59 | }, 60 | 61 | next: buildNext(flush), 62 | clearNext, 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /lib/backburner/utils.ts: -------------------------------------------------------------------------------- 1 | import { IQueueItem } from './interfaces'; 2 | 3 | type MaybeError = Error | undefined; 4 | 5 | const NUMBER = /\d+/; 6 | 7 | const enum QueueItemPosition { 8 | target, 9 | method, 10 | args, 11 | stack 12 | } 13 | 14 | export const QUEUE_ITEM_LENGTH = 5; 15 | export const TIMERS_OFFSET = 7; 16 | 17 | export function isCoercableNumber(suspect) { 18 | let type = typeof suspect; 19 | return type === 'number' && suspect === suspect || type === 'string' && NUMBER.test(suspect); 20 | } 21 | 22 | export function getOnError(options) { 23 | return options.onError || (options.onErrorTarget && options.onErrorTarget[options.onErrorMethod]); 24 | } 25 | 26 | export function findItem(target, method, collection) { 27 | let index = -1; 28 | 29 | for (let i = 0, l = collection.length; i < l; i += QUEUE_ITEM_LENGTH) { 30 | if (collection[i] === target && collection[i + 1] === method) { 31 | index = i; 32 | break; 33 | } 34 | } 35 | 36 | return index; 37 | } 38 | 39 | export function findTimerItem(target, method, collection) { 40 | let index = -1; 41 | 42 | for (let i = 2, l = collection.length; i < l; i += TIMERS_OFFSET) { 43 | if (collection[i] === target && collection[i + 1] === method) { 44 | index = i - 2; 45 | break; 46 | } 47 | } 48 | 49 | return index; 50 | } 51 | 52 | export function getQueueItems(items: any[], queueItemLength: number, queueItemPositionOffset: number = 0): IQueueItem[] { 53 | let queueItems: IQueueItem[] = []; 54 | 55 | for (let i = 0; i < items.length; i += queueItemLength) { 56 | let maybeError: MaybeError = items[i + QueueItemPosition.stack + queueItemPositionOffset]; 57 | let queueItem = { 58 | target: items[i + QueueItemPosition.target + queueItemPositionOffset], 59 | method: items[i + QueueItemPosition.method + queueItemPositionOffset], 60 | args: items[i + QueueItemPosition.args + queueItemPositionOffset], 61 | stack: maybeError !== undefined && 'stack' in maybeError ? maybeError.stack : '' 62 | }; 63 | 64 | queueItems.push(queueItem); 65 | } 66 | 67 | return queueItems; 68 | } 69 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release Process 2 | 3 | Releases are mostly automated using 4 | [release-it](https://github.com/release-it/release-it/) and 5 | [lerna-changelog](https://github.com/lerna/lerna-changelog/). 6 | 7 | ## Preparation 8 | 9 | Since the majority of the actual release process is automated, the primary 10 | remaining task prior to releasing is confirming that all pull requests that 11 | have been merged since the last release have been labeled with the appropriate 12 | `lerna-changelog` labels and the titles have been updated to ensure they 13 | represent something that would make sense to our users. Some great information 14 | on why this is important can be found at 15 | [keepachangelog.com](https://keepachangelog.com/en/1.0.0/), but the overall 16 | guiding principle here is that changelogs are for humans, not machines. 17 | 18 | When reviewing merged PR's the labels to be used are: 19 | 20 | * breaking - Used when the PR is considered a breaking change. 21 | * enhancement - Used when the PR adds a new feature or enhancement. 22 | * bug - Used when the PR fixes a bug included in a previous release. 23 | * documentation - Used when the PR adds or updates documentation. 24 | * internal - Used for internal changes that still require a mention in the 25 | changelog/release notes. 26 | 27 | ## Release 28 | 29 | Once the prep work is completed, the actual release is straight forward: 30 | 31 | * First, ensure that you have installed your projects dependencies: 32 | 33 | ```sh 34 | yarn install 35 | ``` 36 | 37 | * Second, ensure that you have obtained a 38 | [GitHub personal access token][generate-token] with the `repo` scope (no 39 | other permissions are needed). Make sure the token is available as the 40 | `GITHUB_AUTH` environment variable. 41 | 42 | For instance: 43 | 44 | ```bash 45 | export GITHUB_AUTH=abc123def456 46 | ``` 47 | 48 | [generate-token]: https://github.com/settings/tokens/new?scopes=repo&description=GITHUB_AUTH+env+variable 49 | 50 | * And last (but not least 😁) do your release. 51 | 52 | ```sh 53 | npx release-it 54 | ``` 55 | 56 | [release-it](https://github.com/release-it/release-it/) manages the actual 57 | release process. It will prompt you to to choose the version number after which 58 | you will have the chance to hand tweak the changelog to be used (for the 59 | `CHANGELOG.md` and GitHub release), then `release-it` continues on to tagging, 60 | pushing the tag and commits, etc. 61 | -------------------------------------------------------------------------------- /tests/debug-test.ts: -------------------------------------------------------------------------------- 1 | import Backburner from 'backburner'; 2 | 3 | QUnit.module('tests/debug'); 4 | 5 | QUnit.test('schedule - DEBUG flag enables stack tagging', function(assert) { 6 | let bb = new Backburner(['one']); 7 | 8 | bb.schedule('one', () => {}); 9 | if (!bb.currentInstance) { 10 | throw new Error('bb has no instance'); 11 | } 12 | 13 | assert.ok(bb.currentInstance && !bb.currentInstance.queues.one.stackFor(0), 'No stack is recorded'); 14 | 15 | bb.DEBUG = true; 16 | 17 | bb.schedule('one', () => {}); 18 | 19 | if (new Error().stack) { // workaround for CLI runner :( 20 | assert.expect(4); 21 | let stack = bb.currentInstance && bb.currentInstance.queues.one.stackFor(1); 22 | assert.ok(typeof stack === 'string', 'A stack is recorded'); 23 | 24 | let onError = function(error, errorRecordedForStack) { 25 | assert.ok(errorRecordedForStack, 'errorRecordedForStack passed to error function'); 26 | assert.ok(errorRecordedForStack.stack, 'stack is recorded'); 27 | }; 28 | 29 | bb = new Backburner(['errors'], { onError }); 30 | bb.DEBUG = true; 31 | 32 | bb.run(() => { 33 | bb.schedule('errors', () => { 34 | throw new Error('message!'); 35 | }); 36 | }); 37 | } 38 | }); 39 | 40 | QUnit.test('later - DEBUG flag off does not capture stack', function(assert) { 41 | let done = assert.async(); 42 | let onError = function(error, errorRecordedForStack) { 43 | assert.strictEqual(errorRecordedForStack, undefined, 'errorRecordedForStack is not passed to error function when DEBUG is not set'); 44 | done(); 45 | }; 46 | let bb = new Backburner(['one'], { onError }); 47 | 48 | bb.later(() => { 49 | throw new Error('message!'); 50 | }); 51 | }); 52 | 53 | if (new Error().stack) { // workaround for CLI runner :( 54 | QUnit.test('later - DEBUG flag on captures stack', function(assert) { 55 | assert.expect(3); 56 | 57 | let done = assert.async(); 58 | let onError = function(error, errorRecordedForStack) { 59 | assert.ok(errorRecordedForStack, 'errorRecordedForStack passed to error function'); 60 | assert.ok(errorRecordedForStack.stack, 'stack is recorded'); 61 | assert.ok(errorRecordedForStack.stack.indexOf('later') > -1, 'stack includes `later` invocation'); 62 | done(); 63 | }; 64 | let bb = new Backburner(['one'], { onError }); 65 | bb.DEBUG = true; 66 | 67 | bb.later(() => { 68 | throw new Error('message!'); 69 | }); 70 | }); 71 | } 72 | -------------------------------------------------------------------------------- /tests/async-stack-test.ts: -------------------------------------------------------------------------------- 1 | import Backburner from 'backburner'; 2 | 3 | const skipIfNotSupported = !!console['createTask'] ? QUnit.test : QUnit.skip; 4 | 5 | QUnit.module('tests/async_stacks'); 6 | 7 | QUnit.test('schedule - does not affect normal behaviour', function(assert) { 8 | let bb = new Backburner(['one']); 9 | let callCount = 0; 10 | 11 | bb.run(() => { 12 | bb.schedule('one', () => callCount += 1) 13 | bb.schedule('one', () => callCount += 1) 14 | }); 15 | assert.strictEqual(callCount, 2, 'schedule works correctly with ASYNC_STACKS disabled'); 16 | 17 | bb.ASYNC_STACKS = true; 18 | 19 | bb.run(() => { 20 | bb.schedule('one', () => callCount += 1) 21 | bb.schedule('one', () => callCount += 1) 22 | }); 23 | assert.strictEqual(callCount, 4, 'schedule works correctly with ASYNC_STACKS enabled'); 24 | }); 25 | 26 | skipIfNotSupported('schedule - ASYNC_STACKS flag enables async stack tagging', function(assert) { 27 | let bb = new Backburner(['one']); 28 | 29 | bb.schedule('one', () => {}); 30 | 31 | assert.true(bb.currentInstance && (bb.currentInstance.queues.one.consoleTaskFor(0) === undefined), 'No consoleTask is stored'); 32 | 33 | bb.ASYNC_STACKS = true; 34 | 35 | bb.schedule('one', () => {}); 36 | 37 | const task = bb.currentInstance && bb.currentInstance.queues.one.consoleTaskFor(1); 38 | assert.true(!!task?.run, 'consoleTask is stored in queue'); 39 | }); 40 | 41 | QUnit.test('later - ASYNC_STACKS does not affect normal behaviour', function(assert) { 42 | let bb = new Backburner(['one']); 43 | let done = assert.async(); 44 | bb.ASYNC_STACKS = true; 45 | 46 | bb.later(() => { 47 | assert.true(true, 'timer called') 48 | done() 49 | }); 50 | }); 51 | 52 | 53 | skipIfNotSupported('later - skips async stack when ASYNC_STACKS is false', function(assert) { 54 | let done = assert.async(); 55 | let bb = new Backburner(['one']); 56 | 57 | bb.later(() => { 58 | const task = bb.currentInstance && bb.currentInstance.queues.one.consoleTaskFor(0, true); 59 | assert.true(bb.currentInstance && (bb.currentInstance.queues.one.consoleTaskFor(0, true) === undefined), 'consoleTask is not stored') 60 | done(); 61 | }); 62 | }); 63 | 64 | 65 | skipIfNotSupported('later - ASYNC_STACKS flag enables async stack tagging', function(assert) { 66 | let done = assert.async(); 67 | let bb = new Backburner(['one']); 68 | bb.ASYNC_STACKS = true; 69 | 70 | bb.later(() => { 71 | const task = bb.currentInstance && bb.currentInstance.queues.one.consoleTaskFor(0, true); 72 | assert.true(!!task?.run, 'consoleTask is stored in timer queue and then passed to runloop queue') 73 | done(); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /bench/benches/schedule-flush.js: -------------------------------------------------------------------------------- 1 | function prodSetup() { 2 | var backburner = new this.Backburner(["sync", "actions", "routerTransitions", "render", "afterRender", "destroy", "rsvpAfter"]); 3 | 4 | var target = { 5 | someMethod: function() { } 6 | }; 7 | } 8 | 9 | function asyncStackSetup() { 10 | var backburner = new this.Backburner(["sync", "actions", "routerTransitions", "render", "afterRender", "destroy", "rsvpAfter"]); 11 | backburner.ASYNC_STACKS = true; 12 | 13 | var target = { 14 | someMethod: function() { } 15 | }; 16 | } 17 | 18 | function debugSetup() { 19 | var backburner = new this.Backburner(["sync", "actions", "routerTransitions", "render", "afterRender", "destroy", "rsvpAfter"]); 20 | backburner.DEBUG = true; 21 | 22 | var target = { 23 | someMethod: function() { } 24 | }; 25 | } 26 | 27 | let scenarios = []; 28 | 29 | let base = [{ 30 | name: 'Schedule & Flush - function', 31 | 32 | Backburner: Backburner, 33 | 34 | fn: function() { 35 | backburner.begin(); 36 | backburner.schedule('render', function() {}); 37 | backburner.end(); 38 | } 39 | }, { 40 | name: 'Schedule & Flush - target, function', 41 | 42 | Backburner: Backburner, 43 | 44 | fn: function() { 45 | backburner.begin(); 46 | backburner.schedule('render', target, function() {}); 47 | backburner.end(); 48 | } 49 | }, { 50 | name: 'Schedule & Flush - target, string method name', 51 | 52 | Backburner: Backburner, 53 | 54 | fn: function() { 55 | backburner.begin(); 56 | backburner.schedule('render', target, 'someMethod'); 57 | backburner.end(); 58 | } 59 | }, { 60 | name: 'Schedule & Flush - target, string method name, 1 argument', 61 | 62 | Backburner: Backburner, 63 | 64 | fn: function() { 65 | backburner.begin(); 66 | backburner.schedule('render', target, 'someMethod', 'foo'); 67 | backburner.end(); 68 | } 69 | }, { 70 | name: 'Schedule & Flush - target, string method name, 2 arguments', 71 | 72 | Backburner: Backburner, 73 | 74 | fn: function() { 75 | backburner.begin(); 76 | backburner.schedule('render', target, 'someMethod', 'foo', 'bar'); 77 | backburner.end(); 78 | } 79 | }]; 80 | 81 | base.forEach(item => { 82 | let prodItem = Object.assign({}, item); 83 | prodItem.setup = prodSetup; 84 | scenarios.push(prodItem); 85 | }); 86 | 87 | base.forEach(item => { 88 | let debugItem = Object.assign({}, item); 89 | debugItem.name = `ASYNC_STACKS - ${debugItem.name}`; 90 | debugItem.setup = asyncStackSetup; 91 | scenarios.push(debugItem); 92 | }); 93 | 94 | base.forEach(item => { 95 | let debugItem = Object.assign({}, item); 96 | debugItem.name = `DEBUG - ${debugItem.name}`; 97 | debugItem.setup = debugSetup; 98 | scenarios.push(debugItem); 99 | }); 100 | 101 | module.exports = scenarios; 102 | -------------------------------------------------------------------------------- /tests/queue-test.ts: -------------------------------------------------------------------------------- 1 | import Backburner from 'backburner'; 2 | 3 | QUnit.module('tests/queue'); 4 | 5 | QUnit.test('actions scheduled on previous queue, start over from beginning', function(assert) { 6 | assert.expect(5); 7 | 8 | let bb = new Backburner(['one', 'two']); 9 | let step = 0; 10 | 11 | bb.run(function() { 12 | assert.equal(step++, 0, '0'); 13 | 14 | bb.schedule('two', null, function() { 15 | assert.equal(step++, 1, '1'); 16 | 17 | bb.schedule('one', null, function() { 18 | assert.equal(step++, 3, '3'); 19 | }); 20 | }); 21 | 22 | bb.schedule('two', null, function() { 23 | assert.equal(step++, 2, '2'); 24 | }); 25 | }); 26 | 27 | assert.equal(step, 4, '4'); 28 | }); 29 | 30 | QUnit.test('Queue#flush should be recursive if new items are added', function(assert) { 31 | assert.expect(2); 32 | 33 | let bb = new Backburner(['one']); 34 | let count = 0; 35 | 36 | bb.run(function() { 37 | function increment() { 38 | if (++count < 3) { 39 | bb.schedule('one', increment); 40 | } 41 | 42 | if (count === 3) { 43 | 44 | bb.schedule('one', increment); 45 | } 46 | } 47 | 48 | increment(); 49 | assert.equal(count, 1, 'should not have run yet'); 50 | 51 | let currentInstance = bb.currentInstance; 52 | if (currentInstance) { 53 | currentInstance.queues.one.flush(); 54 | } 55 | assert.equal(count, 4, 'should have run all scheduled methods, even ones added during flush'); 56 | }); 57 | 58 | }); 59 | 60 | QUnit.test('Default queue is automatically set to first queue if none is provided', function(assert) { 61 | let bb = new Backburner(['one', 'two']); 62 | assert.equal(bb.defaultQueue, 'one'); 63 | }); 64 | 65 | QUnit.test('Default queue can be manually configured', function(assert) { 66 | let bb = new Backburner(['one', 'two'], { 67 | defaultQueue: 'two' 68 | }); 69 | 70 | assert.equal(bb.defaultQueue, 'two'); 71 | }); 72 | 73 | QUnit.test('onBegin and onEnd are called and passed the correct parameters', function(assert) { 74 | assert.expect(2); 75 | 76 | let befores: (any | null | undefined)[] = []; 77 | let afters: (any | null | undefined)[] = []; 78 | let expectedBefores: (any | null | undefined)[] = []; 79 | let expectedAfters: (any | null | undefined)[] = []; 80 | let outer: any; 81 | let inner: any; 82 | 83 | let bb = new Backburner(['one'], { 84 | onBegin: function(current, previous) { 85 | befores.push(current); 86 | befores.push(previous); 87 | }, 88 | onEnd: function(current, next) { 89 | afters.push(current); 90 | afters.push(next); 91 | } 92 | }); 93 | 94 | bb.run(function() { 95 | outer = bb.currentInstance; 96 | bb.run(function() { 97 | inner = bb.currentInstance; 98 | }); 99 | }); 100 | 101 | expectedBefores = [outer, null, inner, outer]; 102 | expectedAfters = [inner, outer, outer, null]; 103 | 104 | assert.deepEqual(befores, expectedBefores, 'before callbacks successful'); 105 | assert.deepEqual(afters, expectedAfters, 'after callback successful'); 106 | }); 107 | -------------------------------------------------------------------------------- /bench/benches/schedule-cancel.js: -------------------------------------------------------------------------------- 1 | function sharedSetup() { 2 | var backburner = new this.Backburner(["sync", "actions", "routerTransitions", "render", "afterRender", "destroy", "rsvpAfter"]); 3 | 4 | var target = { 5 | someMethod: function() { } 6 | }; 7 | } 8 | 9 | module.exports = [{ 10 | name: 'Schedule & Cancel - function', 11 | 12 | Backburner: Backburner, 13 | 14 | setup: sharedSetup, 15 | 16 | fn: function() { 17 | var timer = backburner.schedule('render', function() { 18 | // no body 19 | }); 20 | backburner.cancel(timer); 21 | } 22 | }, { 23 | name: 'Schedule & Cancel - target, function', 24 | 25 | Backburner: Backburner, 26 | 27 | setup: sharedSetup, 28 | 29 | fn: function() { 30 | var timer = backburner.schedule('render', target, function() { 31 | // no body 32 | }); 33 | backburner.cancel(timer); 34 | } 35 | }, { 36 | name: 'Schedule & Cancel - target, string method name', 37 | 38 | Backburner: Backburner, 39 | 40 | setup: sharedSetup, 41 | 42 | fn: function() { 43 | var timer = backburner.schedule('render', target, 'someMethod'); 44 | backburner.cancel(timer); 45 | } 46 | }, { 47 | name: 'Schedule & Cancel - target, string method name, 1 argument', 48 | 49 | Backburner: Backburner, 50 | 51 | setup: sharedSetup, 52 | 53 | fn: function() { 54 | var timer = backburner.schedule('render', target, 'someMethod', 'foo'); 55 | backburner.cancel(timer); 56 | } 57 | }, { 58 | name: 'Schedule & Cancel - target, string method name, 2 arguments', 59 | 60 | Backburner: Backburner, 61 | 62 | setup: sharedSetup, 63 | 64 | fn: function() { 65 | var timer = backburner.schedule('render', target, 'someMethod', 'foo', 'bar'); 66 | backburner.cancel(timer); 67 | } 68 | }, { 69 | name: 'Schedule & Cancel - prescheduled, same queue - target, string method name', 70 | 71 | Backburner: Backburner, 72 | 73 | setup: function() { 74 | var backburner = new this.Backburner(["sync", "actions", "routerTransitions", "render", "afterRender", "destroy", "rsvpAfter"]); 75 | 76 | var target = { 77 | someMethod: function() { } 78 | }; 79 | 80 | var prescheduleSetupIterations = 100; 81 | while (prescheduleSetupIterations--) { 82 | backburner.schedule('render', function() { }); 83 | } 84 | }, 85 | 86 | fn: function() { 87 | var timer = backburner.schedule('render', target, 'someMethod'); 88 | backburner.cancel(timer); 89 | } 90 | }, { 91 | name: 'Schedule & Cancel - prescheduled, separate queue - target, string method name', 92 | 93 | Backburner: Backburner, 94 | 95 | setup: function() { 96 | var backburner = new this.Backburner(["sync", "actions", "routerTransitions", "render", "afterRender", "destroy", "rsvpAfter"]); 97 | 98 | var target = { 99 | someMethod: function() { } 100 | }; 101 | 102 | var prescheduleSetupIterations = 100; 103 | while (prescheduleSetupIterations--) { 104 | backburner.schedule('actions', function() { }); 105 | } 106 | }, 107 | 108 | fn: function() { 109 | var timer = backburner.schedule('render', target, 'someMethod'); 110 | backburner.cancel(timer); 111 | } 112 | }]; 113 | -------------------------------------------------------------------------------- /tests/queue-debug-info-test.ts: -------------------------------------------------------------------------------- 1 | import Backburner from 'backburner'; 2 | import MockStableError, { 3 | overrideError, 4 | pushStackTrace, 5 | resetError, 6 | } from './utils/mock-stable-error'; 7 | 8 | QUnit.module('tests/queue-debug-info', { 9 | beforeEach: function() { 10 | // @ts-ignore 11 | overrideError(MockStableError); 12 | }, 13 | 14 | afterEach: function() { 15 | resetError(); 16 | } 17 | }); 18 | 19 | QUnit.test('getDebugInfo returns empty array when no debug info is present.', function(assert) { 20 | assert.expect(1); 21 | 22 | let debugInfo; 23 | let bb = new Backburner(['one']); 24 | 25 | bb.run(function() { 26 | debugInfo = bb.currentInstance && bb.currentInstance.queues.one._getDebugInfo(bb.DEBUG); 27 | 28 | assert.equal(debugInfo, undefined, 'DebugInfo is undefined when DEBUG = false'); 29 | }); 30 | }); 31 | 32 | QUnit.test('getDebugInfo returns minimal debug info when one item in queue.', function(assert) { 33 | assert.expect(2); 34 | 35 | let debugInfo; 36 | let method = function() { 37 | assert.ok(true); 38 | }; 39 | let stack = pushStackTrace('Top of stack'); 40 | let bb = new Backburner(['one']); 41 | 42 | bb.DEBUG = true; 43 | 44 | bb.run(function() { 45 | bb.schedule('one', method); 46 | 47 | debugInfo = bb.currentInstance && bb.currentInstance.queues.one._getDebugInfo(bb.DEBUG); 48 | 49 | assert.deepEqual(debugInfo, [{ 50 | target: null, 51 | method, 52 | args: undefined, 53 | stack 54 | }]); 55 | }); 56 | }); 57 | 58 | QUnit.test('getDebugInfo returns full debug info when one item in queue.', function(assert) { 59 | assert.expect(2); 60 | 61 | let debugInfo; 62 | let target = {}; 63 | let method = function() { 64 | assert.ok(true); 65 | }; 66 | let arg1 = 1; 67 | let arg2 = 2; 68 | let stack = pushStackTrace('Top of stack'); 69 | let bb = new Backburner(['one']); 70 | 71 | bb.DEBUG = true; 72 | 73 | bb.run(function() { 74 | bb.schedule('one', target, method, arg1, arg2); 75 | 76 | debugInfo = bb.currentInstance && bb.currentInstance.queues.one._getDebugInfo(bb.DEBUG); 77 | 78 | assert.deepEqual(debugInfo, [{ 79 | target, 80 | method, 81 | args: [arg1, arg2], 82 | stack 83 | }]); 84 | }); 85 | }); 86 | 87 | QUnit.test('getDebugInfo returns debug info when multiple items in queue.', function(assert) { 88 | assert.expect(3); 89 | 90 | let debugInfo; 91 | let method = function() { 92 | assert.ok(true); 93 | }; 94 | let bottom = pushStackTrace('Bottom'); 95 | let top = pushStackTrace('Top'); 96 | let bb = new Backburner(['one']); 97 | 98 | bb.DEBUG = true; 99 | 100 | bb.run(function() { 101 | bb.schedule('one', method); 102 | bb.schedule('one', method); 103 | 104 | debugInfo = bb.currentInstance && bb.currentInstance.queues.one._getDebugInfo(bb.DEBUG); 105 | 106 | assert.deepEqual(debugInfo, [ 107 | { 108 | target: null, 109 | method, 110 | args: undefined, 111 | stack: top 112 | }, 113 | { 114 | target: null, 115 | method, 116 | args: undefined, 117 | stack: bottom 118 | } 119 | ]); 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /lib/backburner/deferred-action-queues.ts: -------------------------------------------------------------------------------- 1 | import { IQueueItem } from './interfaces'; 2 | import Queue, { QUEUE_STATE } from './queue'; 3 | 4 | export interface IDebugInfo { 5 | [key: string]: IQueueItem[] | undefined; 6 | } 7 | 8 | export default class DeferredActionQueues { 9 | public queues: { [name: string]: Queue } = {}; 10 | public queueNameIndex = 0; 11 | 12 | private queueNames: string[]; 13 | 14 | constructor(queueNames: string[] = [], options: any) { 15 | this.queueNames = queueNames; 16 | 17 | queueNames.reduce(function(queues, queueName) { 18 | queues[queueName] = new Queue(queueName, options[queueName], options); 19 | return queues; 20 | }, this.queues); 21 | } 22 | 23 | /** 24 | * @method schedule 25 | * @param {String} queueName 26 | * @param {Any} target 27 | * @param {Any} method 28 | * @param {Any} args 29 | * @param {Boolean} onceFlag 30 | * @param {Any} stack 31 | * @return queue 32 | */ 33 | public schedule(queueName: string, target: any, method: any, args: any, onceFlag: boolean, stack: any, consoleTask: any) { 34 | let queues = this.queues; 35 | let queue = queues[queueName]; 36 | 37 | if (queue === undefined) { 38 | throw new Error(`You attempted to schedule an action in a queue (${queueName}) that doesn\'t exist`); 39 | } 40 | 41 | if (method === undefined || method === null) { 42 | throw new Error(`You attempted to schedule an action in a queue (${queueName}) for a method that doesn\'t exist`); 43 | } 44 | 45 | this.queueNameIndex = 0; 46 | 47 | if (onceFlag) { 48 | return queue.pushUnique(target, method, args, stack, consoleTask); 49 | } else { 50 | return queue.push(target, method, args, stack, consoleTask); 51 | } 52 | } 53 | 54 | /** 55 | * DeferredActionQueues.flush() calls Queue.flush() 56 | * 57 | * @method flush 58 | * @param {Boolean} fromAutorun 59 | */ 60 | public flush(fromAutorun = false) { 61 | let queue; 62 | let queueName; 63 | let numberOfQueues = this.queueNames.length; 64 | 65 | while (this.queueNameIndex < numberOfQueues) { 66 | queueName = this.queueNames[this.queueNameIndex]; 67 | queue = this.queues[queueName]; 68 | 69 | if (queue.hasWork() === false) { 70 | this.queueNameIndex++; 71 | if (fromAutorun && this.queueNameIndex < numberOfQueues) { 72 | return QUEUE_STATE.Pause; 73 | } 74 | } else { 75 | if (queue.flush(false /* async */) === QUEUE_STATE.Pause) { 76 | return QUEUE_STATE.Pause; 77 | } 78 | } 79 | } 80 | } 81 | 82 | /** 83 | * Returns debug information for the current queues. 84 | * 85 | * @method _getDebugInfo 86 | * @param {Boolean} debugEnabled 87 | * @returns {IDebugInfo | undefined} 88 | */ 89 | public _getDebugInfo(debugEnabled: boolean): IDebugInfo | undefined { 90 | if (debugEnabled) { 91 | let debugInfo: IDebugInfo = {}; 92 | let queue: Queue; 93 | let queueName: string; 94 | let numberOfQueues: number = this.queueNames.length; 95 | let i: number = 0; 96 | 97 | while (i < numberOfQueues) { 98 | queueName = this.queueNames[i]; 99 | queue = this.queues[queueName]; 100 | 101 | debugInfo[queueName] = queue._getDebugInfo(debugEnabled); 102 | i++; 103 | } 104 | 105 | return debugInfo; 106 | } 107 | 108 | return; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /tests/multi-turn-test.ts: -------------------------------------------------------------------------------- 1 | import Backburner, { buildPlatform } from 'backburner'; 2 | 3 | QUnit.module('tests/multi-turn'); 4 | 5 | const queue: any[] = []; 6 | let platform; 7 | function buildFakePlatform(flush) { 8 | platform = buildPlatform(flush); 9 | platform.flushSync = function() { 10 | flush(); 11 | }; 12 | return platform; 13 | } 14 | 15 | QUnit.test('basic', function(assert) { 16 | let bb = new Backburner(['zomg'], { 17 | // This is just a place holder for now, but somehow the system needs to 18 | // know to when to stop 19 | mustYield() { 20 | return true; // yield after each step, for now. 21 | }, 22 | _buildPlatform: buildFakePlatform 23 | }); 24 | 25 | let order = -1; 26 | let tasks = { 27 | one: { count: 0, order: -1 }, 28 | two: { count: 0, order: -1 }, 29 | three: { count: 0, order: -1 } 30 | }; 31 | 32 | bb.schedule('zomg', null, () => { 33 | tasks.one.count++; 34 | tasks.one.order = ++order; 35 | }); 36 | 37 | bb.schedule('zomg', null, () => { 38 | tasks.two.count++; 39 | tasks.two.order = ++order; 40 | }); 41 | 42 | bb.schedule('zomg', null, () => { 43 | tasks.three.count++; 44 | tasks.three.order = ++order; 45 | }); 46 | 47 | assert.deepEqual(tasks, { 48 | one: { count: 0, order: -1 }, 49 | two: { count: 0, order: -1 }, 50 | three: { count: 0, order: -1 } 51 | }, 'no tasks have been run before the platform flushes'); 52 | 53 | platform.flushSync(); 54 | 55 | assert.deepEqual(tasks, { 56 | one: { count: 1, order: 0 }, 57 | two: { count: 0, order: -1 }, 58 | three: { count: 0, order: -1 } 59 | }, 'TaskOne has been run before the platform flushes'); 60 | 61 | platform.flushSync(); 62 | 63 | assert.deepEqual(tasks, { 64 | one: { count: 1, order: 0 }, 65 | two: { count: 1, order: 1 }, 66 | three: { count: 0, order: -1 } 67 | }, 'TaskOne and TaskTwo has been run before the platform flushes'); 68 | 69 | platform.flushSync(); 70 | 71 | assert.deepEqual(tasks, { 72 | one: { count: 1, order: 0 }, 73 | two: { count: 1, order: 1 }, 74 | three: { count: 1, order: 2 } 75 | }, 'TaskOne, TaskTwo and TaskThree has been run before the platform flushes'); 76 | }); 77 | 78 | QUnit.test('properly cancel items which are added during flush', function(assert) { 79 | let bb = new Backburner(['zomg'], { 80 | // This is just a place holder for now, but somehow the system needs to 81 | // know to when to stop 82 | mustYield() { 83 | return true; // yield after each step, for now. 84 | }, 85 | 86 | _buildPlatform: buildFakePlatform 87 | }); 88 | 89 | let fooCalled = 0; 90 | let barCalled = 0; 91 | 92 | let obj1 = { 93 | foo() { 94 | fooCalled++; 95 | } 96 | }; 97 | 98 | let obj2 = { 99 | bar() { 100 | barCalled++; 101 | } 102 | }; 103 | 104 | bb.scheduleOnce('zomg', obj1, 'foo'); 105 | bb.scheduleOnce('zomg', obj1, 'foo'); 106 | bb.scheduleOnce('zomg', obj2, 'bar'); 107 | bb.scheduleOnce('zomg', obj2, 'bar'); 108 | 109 | platform.flushSync(); 110 | 111 | let timer1 = bb.scheduleOnce('zomg', obj1, 'foo'); 112 | let timer2 = bb.scheduleOnce('zomg', obj2, 'bar'); 113 | bb.cancel(timer1); 114 | bb.cancel(timer2); 115 | 116 | platform.flushSync(); 117 | platform.flushSync(); 118 | platform.flushSync(); 119 | 120 | assert.equal(fooCalled, 1, 'fooCalled'); 121 | assert.equal(barCalled, 1, 'barCalled'); 122 | 123 | }); 124 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # backburner.js [![Build Status](https://travis-ci.org/BackburnerJS/backburner.js.svg?branch=master)](https://travis-ci.org/BackburnerJS/backburner.js) 2 | A rewrite of the Ember.js run loop as a generic microlibrary. 3 | 4 | ## TL;DR 5 | A priority queue that will efficiently batch, order, reorder and process work; done via scheduling work on specific queues. 6 | 7 | ## API 8 | 9 | ### Constructor 10 | 11 | | Constructor | Description | 12 | |---|---| 13 | | `new Backburner()` | instantiate a Backburner instance with an array of queue names | 14 | 15 | ### Instance methods 16 | 17 | | Method | Description | 18 | |---|---| 19 | | `Backburner#run` | execute the passed function and flush any deferred actions | 20 | | `Backburner#defer` | defer the passed function to run inside the specified queue | 21 | | `Backburner#deferOnce` | defer the passed function to run inside the specified queue, only execute it once | 22 | | `Backburner#setTimeout` | execute the passed function in a specified amount of time | 23 | | `Backburner#debounce` | execute the passed function in a specified amount of time, reset timer upon additional calls | 24 | | `Backburner#throttle` | rate-limit the passed function for a specified amount of time | 25 | | `Backburner#cancel` | cancel a `deferOnce`, `setTimeout`, `debounce` or `throttle` | 26 | | `Backburner#on` | Add an event callback. Supports the following events:
| 27 | | `Backburner#off` | Removes an event callback | 28 | | `Backburner#join` | Join the passed method with an existing queue and execute immediately, if there isn't one use `Backburner#run` | 29 | | `Backburner#getDebugInfo` | Returns debug information for counters, timers and queues, which includes surfacing the stack trace information for the respective calls | 30 | 31 | #### Alias 32 | 33 | | Alias | Description | 34 | |---|---| 35 | | `Backburner#schedule` | same as `defer` | 36 | | `Backburner#scheduleOnce` | same as `deferOnce` | 37 | | `Backburner#later` | same as `setTimeout` | 38 | 39 | ## Example usage 40 | 41 | The following code will only cause a single DOM manipulation: 42 | 43 | ```html 44 | 45 | 46 | 47 | 48 | Backburner demo 49 | 50 | 51 | 52 |
53 | 54 | 75 | 76 | 77 | ``` 78 | 79 | ## Benchmarks 80 | 81 | The `/bench` directory includes a number of performance benchmarks. 82 | 83 | To run in `node`: 84 | 85 | ```bash 86 | yarn ember build 87 | node ./bench/index.js # (all benchmarks) 88 | node ./bench/index.js ./bench/benches/some-file.js # (specific benchmark) 89 | ``` 90 | 91 | Or to run in a browser, run `yarn ember server`, and visit `http://localhost:4200` in a browser. Be aware that having the developer tools open/closed can affect JS performance. 92 | -------------------------------------------------------------------------------- /tests/configurable-timeout-test.ts: -------------------------------------------------------------------------------- 1 | import Backburner, { buildPlatform } from 'backburner'; 2 | 3 | QUnit.module('tests/configurable-timeout'); 4 | 5 | QUnit.test('We can configure a custom platform', function(assert) { 6 | assert.expect(1); 7 | 8 | let bb = new Backburner(['one'], { 9 | _buildPlatform(flush) { 10 | let platform = buildPlatform(flush); 11 | platform['isFakePlatform'] = true; 12 | return platform; 13 | } 14 | }); 15 | 16 | assert.ok(bb['_platform']!['isFakePlatform'], 'We can pass in a custom platform'); 17 | }); 18 | 19 | QUnit.test('We can use a custom setTimeout', function(assert) { 20 | assert.expect(1); 21 | let done = assert.async(); 22 | 23 | let customNextWasUsed = false; 24 | let bb = new Backburner(['one'], { 25 | _buildPlatform(flush) { 26 | return { 27 | next() { 28 | throw new TypeError('NOT IMPLEMENTED'); 29 | }, 30 | clearNext() { }, 31 | setTimeout(cb) { 32 | customNextWasUsed = true; 33 | return setTimeout(cb); 34 | }, 35 | clearTimeout(timer) { 36 | return clearTimeout(timer); 37 | }, 38 | now() { 39 | return Date.now(); 40 | }, 41 | isFakePlatform: true 42 | }; 43 | } 44 | }); 45 | 46 | bb.setTimeout(() => { 47 | assert.ok(customNextWasUsed , 'custom later was used'); 48 | done(); 49 | }); 50 | }); 51 | 52 | QUnit.test('We can use a custom next', function(assert) { 53 | assert.expect(1); 54 | let done = assert.async(); 55 | 56 | let customNextWasUsed = false; 57 | let bb = new Backburner(['one'], { 58 | _buildPlatform(flush) { 59 | return { 60 | setTimeout() { 61 | throw new TypeError('NOT IMPLEMENTED'); 62 | }, 63 | clearTimeout(timer) { 64 | return clearTimeout(timer); 65 | }, 66 | next() { 67 | // next is used for the autorun 68 | customNextWasUsed = true; 69 | return setTimeout(flush); 70 | }, 71 | clearNext() { }, 72 | now() { return Date.now(); }, 73 | isFakePlatform: true 74 | }; 75 | } 76 | }); 77 | 78 | bb.scheduleOnce('one', () => { 79 | assert.ok(customNextWasUsed , 'custom later was used'); 80 | done(); 81 | }); 82 | }); 83 | 84 | QUnit.test('We can use a custom clearTimeout', function(assert) { 85 | assert.expect(2); 86 | 87 | let functionWasCalled = false; 88 | let customClearTimeoutWasUsed = false; 89 | let bb = new Backburner(['one'], { 90 | _buildPlatform(flush) { 91 | return { 92 | setTimeout(method, wait) { 93 | return setTimeout(method, wait); 94 | }, 95 | clearTimeout(timer) { 96 | customClearTimeoutWasUsed = true; 97 | return clearTimeout(timer); 98 | }, 99 | next() { 100 | return setTimeout(flush, 0); 101 | }, 102 | clearNext() { 103 | customClearTimeoutWasUsed = true; 104 | }, 105 | now() { 106 | return Date.now(); 107 | } 108 | }; 109 | } 110 | }); 111 | 112 | bb.scheduleOnce('one', () => functionWasCalled = true); 113 | bb.cancelTimers(); 114 | 115 | bb.run(() => { 116 | bb.scheduleOnce('one', () => { 117 | assert.ok(!functionWasCalled, 'function was not called'); 118 | assert.ok(customClearTimeoutWasUsed, 'custom clearTimeout was used'); 119 | }); 120 | }); 121 | }); 122 | 123 | QUnit.test('We can use a custom now', function(assert) { 124 | assert.expect(1); 125 | let done = assert.async(); 126 | 127 | let currentTime = 10; 128 | let customNowWasUsed = false; 129 | let bb = new Backburner(['one'], { 130 | _buildPlatform(flush) { 131 | return { 132 | setTimeout(method, wait) { 133 | return setTimeout(method, wait); 134 | }, 135 | clearTimeout(id) { 136 | clearTimeout(id); 137 | }, 138 | next() { 139 | return setTimeout(flush, 0); 140 | }, 141 | clearNext() { }, 142 | now() { 143 | customNowWasUsed = true; 144 | return currentTime += 10; 145 | }, 146 | }; 147 | } 148 | }); 149 | 150 | bb.later(() => { 151 | assert.ok(customNowWasUsed , 'custom now was used'); 152 | done(); 153 | }, 10); 154 | }); 155 | -------------------------------------------------------------------------------- /tests/events-test.ts: -------------------------------------------------------------------------------- 1 | import Backburner from 'backburner'; 2 | 3 | QUnit.module('tests/events'); 4 | 5 | QUnit.test('end event should fire after runloop completes', function(assert) { 6 | assert.expect(3); 7 | let callNumber = 0; 8 | 9 | let bb = new Backburner(['one', 'two']); 10 | 11 | bb.on('end', () => callNumber++); 12 | 13 | function funcOne() { 14 | assert.equal(callNumber, 0); 15 | } 16 | 17 | function funcTwo() { 18 | assert.equal(callNumber, 0); 19 | } 20 | 21 | bb.run(() => { 22 | bb.schedule('one', null, funcOne); 23 | bb.schedule('two', null, funcTwo); 24 | }); 25 | 26 | assert.equal(callNumber, 1); 27 | }); 28 | 29 | QUnit.test('end event should fire before onEnd', function(assert) { 30 | assert.expect(3); 31 | let callNumber = 0; 32 | 33 | let bb = new Backburner(['one', 'two'], { 34 | onEnd() { 35 | assert.equal(callNumber, 1); 36 | } 37 | }); 38 | 39 | bb.on('end', () => callNumber++); 40 | 41 | function funcOne() { 42 | assert.equal(callNumber, 0); 43 | } 44 | 45 | function funcTwo() { 46 | assert.equal(callNumber, 0); 47 | } 48 | 49 | bb.run(() => { 50 | bb.schedule('one', null, funcOne); 51 | bb.schedule('two', null, funcTwo); 52 | }); 53 | }); 54 | 55 | QUnit.test('end event should be passed the current and next instance', function(assert) { 56 | assert.expect(4); 57 | let callNumber = 0; 58 | 59 | let firstArgument = null; 60 | let secondArgument = null; 61 | 62 | let bb = new Backburner(['one'], { 63 | onEnd(first, second) { 64 | assert.equal(firstArgument, first); 65 | assert.equal(secondArgument, second); 66 | } 67 | }); 68 | 69 | bb.on('end', (first, second) => { 70 | firstArgument = first; 71 | secondArgument = second; 72 | }); 73 | 74 | bb.run(() => bb.schedule('one', null, () => {})); 75 | bb.run(() => bb.schedule('one', null, () => {})); 76 | }); 77 | // blah 78 | 79 | QUnit.test('begin event should fire before runloop begins', function(assert) { 80 | assert.expect(4); 81 | let callNumber = 0; 82 | 83 | let bb = new Backburner(['one', 'two']); 84 | 85 | bb.on('begin', () => callNumber++); 86 | 87 | function funcOne() { 88 | assert.equal(callNumber, 1); 89 | } 90 | 91 | function funcTwo() { 92 | assert.equal(callNumber, 1); 93 | } 94 | 95 | assert.equal(callNumber, 0); 96 | bb.run(() => { 97 | bb.schedule('one', null, funcOne); 98 | bb.schedule('two', null, funcTwo); 99 | }); 100 | 101 | assert.equal(callNumber, 1); 102 | }); 103 | 104 | QUnit.test('begin event should fire before onBegin', function(assert) { 105 | assert.expect(1); 106 | let callNumber = 0; 107 | 108 | let bb = new Backburner(['one', 'two'], { 109 | onBegin() { 110 | assert.equal(callNumber, 1); 111 | } 112 | }); 113 | 114 | bb.on('begin', () => callNumber++); 115 | 116 | bb.run(() => { 117 | bb.schedule('one', null, () => {}); 118 | bb.schedule('two', null, () => {}); 119 | }); 120 | }); 121 | 122 | QUnit.test('begin event should be passed the current and previous instance', function(assert) { 123 | assert.expect(4); 124 | let callNumber = 0; 125 | 126 | let firstArgument = null; 127 | let secondArgument = null; 128 | 129 | let bb = new Backburner(['one'], { 130 | onBegin(first, second) { 131 | assert.equal(firstArgument, first); 132 | assert.equal(secondArgument, second); 133 | } 134 | }); 135 | 136 | bb.on('begin', (first, second) => { 137 | firstArgument = first; 138 | secondArgument = second; 139 | }); 140 | 141 | bb.run(() => bb.schedule('one', null, () => {})); 142 | bb.run(() => bb.schedule('one', null, () => {})); 143 | }); 144 | 145 | // blah 146 | QUnit.test('events should work with multiple callbacks', function(assert) { 147 | assert.expect(2); 148 | let firstCalled = false; 149 | let secondCalled = false; 150 | 151 | let bb = new Backburner(['one']); 152 | 153 | function first() { 154 | firstCalled = true; 155 | } 156 | 157 | function second() { 158 | secondCalled = true; 159 | } 160 | 161 | bb.on('end', first); 162 | bb.on('end', second); 163 | 164 | bb.run(() => bb.schedule('one', null, () => {})); 165 | 166 | assert.equal(secondCalled, true); 167 | assert.equal(firstCalled, true); 168 | }); 169 | 170 | QUnit.test('off should unregister specific callback', function(assert) { 171 | assert.expect(2); 172 | let firstCalled = false; 173 | let secondCalled = false; 174 | 175 | let bb = new Backburner(['one']); 176 | 177 | function first() { 178 | firstCalled = true; 179 | } 180 | 181 | function second() { 182 | secondCalled = true; 183 | } 184 | 185 | bb.on('end', first); 186 | bb.on('end', second); 187 | 188 | bb.off('end', first); 189 | 190 | bb.run(() => bb.schedule('one', null, () => {})); 191 | 192 | assert.equal(secondCalled, true); 193 | assert.equal(firstCalled, false); 194 | }); 195 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | /* globals module, require */ 2 | 'use strict'; 3 | const MergeTrees = require('broccoli-merge-trees'); 4 | const Funnel = require('broccoli-funnel'); 5 | const Rollup = require('broccoli-rollup'); 6 | const resolve = require('rollup-plugin-node-resolve'); 7 | const commonjs = require('rollup-plugin-commonjs'); 8 | const path = require('path'); 9 | const typescript = require('broccoli-typescript-compiler').default; 10 | const buble = require('rollup-plugin-buble'); 11 | const fs = require('fs'); 12 | 13 | const SOURCE_MAPPING_DATA_URL = '//# sourceMap' + 'pingURL=data:application/json;base64,'; 14 | 15 | module.exports = function (app) { 16 | const src = new MergeTrees([ 17 | new Funnel(__dirname + '/lib', { 18 | destDir: 'lib' 19 | }), 20 | new Funnel(__dirname + '/tests', { 21 | destDir: 'tests' 22 | }) 23 | ]); 24 | 25 | const compiled = typescript(src, { 26 | throwOnError: process.env.EMBER_ENV === 'production', 27 | }); 28 | 29 | const backburner = new Rollup(compiled, { 30 | rollup: { 31 | input: 'lib/index.js', 32 | output: { 33 | file: 'es6/backburner.js', 34 | format: 'es', 35 | sourcemap: true 36 | }, 37 | format: 'es', 38 | plugins: [ 39 | loadWithInlineMap() 40 | ] 41 | } 42 | }); 43 | 44 | return new MergeTrees([ 45 | backburner, 46 | new Rollup(compiled, { 47 | rollup: { 48 | input: 'lib/index.js', 49 | plugins: [ 50 | loadWithInlineMap(), 51 | buble() 52 | ], 53 | output: [{ 54 | file: 'named-amd/backburner.js', 55 | exports: 'named', 56 | format: 'amd', 57 | amd: { id: 'backburner' }, 58 | sourcemap: true 59 | }, { 60 | file: 'backburner.js', 61 | format: 'cjs', 62 | sourcemap: true 63 | }] 64 | } 65 | }), 66 | new Rollup(compiled, { 67 | annotation: 'named-amd/tests.js', 68 | rollup: { 69 | input: 'tests/index.js', 70 | external: ['backburner'], 71 | plugins: [ 72 | // just used to resolve lolex, which has a malformed `modules` entry point 73 | resolve({ module: false, main: true }), 74 | commonjs(), 75 | loadWithInlineMap(), 76 | buble() 77 | ], 78 | output: [{ 79 | file: 'named-amd/tests.js', 80 | format: 'amd', 81 | amd: { id: 'backburner-tests' }, 82 | sourcemap: true 83 | }] 84 | } 85 | }), 86 | new Funnel(path.dirname(require.resolve('qunit')), { 87 | annotation: 'tests/qunit.{js,css}', 88 | destDir: 'tests', 89 | files: ['qunit.css', 'qunit.js'] 90 | }), 91 | new Funnel(path.dirname(require.resolve('loader.js')), { 92 | annotation: 'loader.js', 93 | destDir: '', 94 | files: ['loader.js'] 95 | }), 96 | new Funnel(compiled, { 97 | destDir: '/', 98 | include: ['lib/**/*.d.ts'], 99 | 100 | getDestinationPath: function(relativePath) { 101 | let path = relativePath.substring(4); // Slice off lib 102 | if (path === 'index.d.ts') { 103 | return 'backburner.d.ts'; 104 | } 105 | return path; 106 | } 107 | }), 108 | new Funnel(__dirname + '/tests', { 109 | destDir: 'tests', 110 | files: ['index.html'] 111 | }), 112 | new Funnel(__dirname + "/bench", { 113 | destDir: "bench", 114 | files: ["index.html"], 115 | }), 116 | new Rollup(__dirname + "/bench", { 117 | rollup: { 118 | treeshake: false, 119 | input: "browser-bench.js", 120 | external: ["backburner"], 121 | plugins: [ 122 | resolve(), 123 | commonjs(), 124 | loadWithInlineMap(), 125 | ], 126 | output: [ 127 | { 128 | file: "bench/browser-bench.js", 129 | format: "amd", 130 | amd: { id: "browser-bench" }, 131 | sourcemap: true, 132 | }, 133 | ], 134 | }, 135 | }), 136 | new Funnel(path.dirname(require.resolve("lodash")), { 137 | destDir: "bench", 138 | files: ["lodash.js"], 139 | }), 140 | ], { 141 | annotation: 'dist' 142 | }); 143 | }; 144 | 145 | function loadWithInlineMap() { 146 | return { 147 | load: function (id) { 148 | var code = fs.readFileSync(id, 'utf8'); 149 | var result = { 150 | code: code, 151 | map: null 152 | }; 153 | var index = code.lastIndexOf(SOURCE_MAPPING_DATA_URL); 154 | if (index === -1) { 155 | return result; 156 | } 157 | result.code = code.slice(0, index); 158 | result.map = parseSourceMap(code.slice(index + SOURCE_MAPPING_DATA_URL.length)); 159 | result.file = id; 160 | return result; 161 | } 162 | }; 163 | } 164 | 165 | function parseSourceMap(base64) { 166 | return JSON.parse(new Buffer(base64, 'base64').toString('utf8')); 167 | } 168 | -------------------------------------------------------------------------------- /tests/run-test.ts: -------------------------------------------------------------------------------- 1 | import Backburner from 'backburner'; 2 | 3 | QUnit.module('tests/run'); 4 | 5 | QUnit.test('when passed a function', function(assert) { 6 | assert.expect(1); 7 | 8 | let bb = new Backburner(['one']); 9 | let functionWasCalled = false; 10 | 11 | bb.run(() => functionWasCalled = true); 12 | 13 | assert.ok(functionWasCalled, 'function was called'); 14 | }); 15 | 16 | QUnit.test('when passed a target and method', function(assert) { 17 | assert.expect(2); 18 | 19 | let bb = new Backburner(['one']); 20 | let functionWasCalled = false; 21 | 22 | bb.run({zomg: 'hi'}, function() { 23 | assert.equal(this.zomg, 'hi', 'the target was properly set'); 24 | functionWasCalled = true; 25 | }); 26 | 27 | assert.ok(functionWasCalled, 'function was called'); 28 | }); 29 | 30 | QUnit.test('when passed a target, method, and arguments', function(assert) { 31 | assert.expect(5); 32 | 33 | let bb = new Backburner(['one']); 34 | let functionWasCalled = false; 35 | 36 | bb.run({zomg: 'hi'}, function(a, b, c) { 37 | assert.equal(this.zomg, 'hi', 'the target was properly set'); 38 | assert.equal(a, 1, 'the first arguments was passed in'); 39 | assert.equal(b, 2, 'the second arguments was passed in'); 40 | assert.equal(c, 3, 'the third arguments was passed in'); 41 | functionWasCalled = true; 42 | }, 1, 2, 3); 43 | 44 | assert.ok(functionWasCalled, 'function was called'); 45 | }); 46 | 47 | QUnit.test('nesting run loops preserves the stack', function(assert) { 48 | assert.expect(10); 49 | 50 | let bb = new Backburner(['one']); 51 | let outerBeforeFunctionWasCalled = false; 52 | let middleBeforeFunctionWasCalled = false; 53 | let innerFunctionWasCalled = false; 54 | let middleAfterFunctionWasCalled = false; 55 | let outerAfterFunctionWasCalled = false; 56 | 57 | bb.run(() => { 58 | bb.schedule('one', () => { 59 | outerBeforeFunctionWasCalled = true; 60 | }); 61 | 62 | bb.run(() => { 63 | bb.schedule('one', () => { 64 | middleBeforeFunctionWasCalled = true; 65 | }); 66 | 67 | bb.run(() => { 68 | bb.schedule('one', () => { 69 | innerFunctionWasCalled = true; 70 | }); 71 | assert.ok(!innerFunctionWasCalled, 'function is deferred'); 72 | }); 73 | assert.ok(innerFunctionWasCalled, 'function is called'); 74 | 75 | bb.schedule('one', () => { 76 | middleAfterFunctionWasCalled = true; 77 | }); 78 | 79 | assert.ok(!middleBeforeFunctionWasCalled, 'function is deferred'); 80 | assert.ok(!middleAfterFunctionWasCalled, 'function is deferred'); 81 | }); 82 | 83 | assert.ok(middleBeforeFunctionWasCalled, 'function is called'); 84 | assert.ok(middleAfterFunctionWasCalled, 'function is called'); 85 | 86 | bb.schedule('one', () => { 87 | outerAfterFunctionWasCalled = true; 88 | }); 89 | 90 | assert.ok(!outerBeforeFunctionWasCalled, 'function is deferred'); 91 | assert.ok(!outerAfterFunctionWasCalled, 'function is deferred'); 92 | }); 93 | 94 | assert.ok(outerBeforeFunctionWasCalled, 'function is called'); 95 | assert.ok(outerAfterFunctionWasCalled, 'function is called'); 96 | }); 97 | 98 | QUnit.test('runs can be nested', function(assert) { 99 | assert.expect(2); 100 | 101 | let bb = new Backburner(['one']); 102 | let step = 0; 103 | 104 | bb.run(() => { 105 | assert.equal(step++, 0); 106 | 107 | bb.run(() => { 108 | assert.equal(step++, 1); 109 | }); 110 | }); 111 | }); 112 | 113 | QUnit.test('run returns value', function(assert) { 114 | let bb = new Backburner(['one']); 115 | let value = bb.run(() => 'hi'); 116 | 117 | assert.equal(value, 'hi'); 118 | }); 119 | 120 | QUnit.test('onError', function(assert) { 121 | assert.expect(1); 122 | 123 | function onError(error) { 124 | assert.equal('QUnit.test error', error.message); 125 | } 126 | 127 | let bb = new Backburner(['errors'], { 128 | onError: onError 129 | }); 130 | 131 | bb.run(() => { 132 | throw new Error('QUnit.test error'); 133 | }); 134 | }); 135 | 136 | QUnit.test('onError set after start', function(assert) { 137 | assert.expect(2); 138 | 139 | let bb = new Backburner(['errors']); 140 | 141 | bb.run(() => assert.ok(true)); 142 | 143 | bb.options.onError = function(error) { 144 | assert.equal('QUnit.test error', error.message); 145 | }; 146 | 147 | bb.run(() => { throw new Error('QUnit.test error'); }); 148 | }); 149 | 150 | QUnit.test('onError with target and action', function(assert) { 151 | assert.expect(3); 152 | 153 | let target = {}; 154 | 155 | let bb = new Backburner(['errors'], { 156 | onErrorTarget: target, 157 | onErrorMethod: 'onerror' 158 | }); 159 | 160 | bb.run(() => assert.ok(true)); 161 | 162 | target['onerror'] = function(error) { 163 | assert.equal('QUnit.test error', error.message); 164 | }; 165 | 166 | bb.run(() => { throw new Error('QUnit.test error'); }); 167 | 168 | target['onerror'] = function() { }; 169 | 170 | bb.run(() => { throw new Error('QUnit.test error'); }); 171 | 172 | target['onerror'] = function(error) { 173 | assert.equal('QUnit.test error', error.message); 174 | }; 175 | 176 | bb.run(() => { throw new Error('QUnit.test error'); }); 177 | }); 178 | 179 | QUnit.test('when [callback, string] args passed', function(assert) { 180 | assert.expect(2); 181 | 182 | let bb = new Backburner(['one']); 183 | let functionWasCalled = false; 184 | 185 | bb.run(function(name) { 186 | assert.equal(name, 'batman'); 187 | functionWasCalled = true; 188 | }, 'batman'); 189 | 190 | assert.ok(functionWasCalled, 'function was called'); 191 | }); 192 | -------------------------------------------------------------------------------- /tests/join-test.ts: -------------------------------------------------------------------------------- 1 | import Backburner from 'backburner'; 2 | 3 | QUnit.module('tests/join'); 4 | 5 | function depth(bb) { 6 | return bb.instanceStack.length + (bb.currentInstance ? 1 : 0); 7 | } 8 | 9 | QUnit.test('outside of a run loop', function(assert) { 10 | assert.expect(4); 11 | 12 | let bb = new Backburner(['one']); 13 | 14 | assert.equal(depth(bb), 0); 15 | let result = bb.join(() => { 16 | assert.equal(depth(bb), 1); 17 | return 'result'; 18 | }); 19 | assert.equal(result, 'result'); 20 | assert.equal(depth(bb), 0); 21 | }); 22 | 23 | QUnit.test('inside of a run loop', function(assert) { 24 | assert.expect(4); 25 | 26 | let bb = new Backburner(['one']); 27 | 28 | assert.equal(depth(bb), 0); 29 | bb.run(() => { 30 | let result = bb.join(() => { 31 | assert.equal(depth(bb), 1); 32 | return 'result'; 33 | }); 34 | assert.equal(result, 'result'); 35 | }); 36 | assert.equal(depth(bb), 0); 37 | }); 38 | 39 | QUnit.test('nested join calls', function(assert) { 40 | assert.expect(7); 41 | 42 | let bb = new Backburner(['one']); 43 | 44 | assert.equal(depth(bb), 0); 45 | bb.join(() => { 46 | assert.equal(depth(bb), 1); 47 | bb.join(() => { 48 | assert.equal(depth(bb), 1); 49 | bb.join(() => { 50 | assert.equal(depth(bb), 1); 51 | }); 52 | assert.equal(depth(bb), 1); 53 | }); 54 | assert.equal(depth(bb), 1); 55 | }); 56 | assert.equal(depth(bb), 0); 57 | }); 58 | 59 | QUnit.test('nested run loops', function(assert) { 60 | assert.expect(7); 61 | 62 | let bb = new Backburner(['one']); 63 | 64 | assert.equal(depth(bb), 0); 65 | bb.join(() => { 66 | assert.equal(depth(bb), 1); 67 | bb.run(() => { 68 | assert.equal(depth(bb), 2); 69 | bb.join(() => { 70 | assert.equal(depth(bb), 2); 71 | }); 72 | assert.equal(depth(bb), 2); 73 | }); 74 | assert.equal(depth(bb), 1); 75 | }); 76 | assert.equal(depth(bb), 0); 77 | }); 78 | 79 | QUnit.test('queue execution order', function(assert) { 80 | assert.expect(1); 81 | 82 | let bb = new Backburner(['one']); 83 | let items: number[] = []; 84 | 85 | bb.run(() => { 86 | items.push(0); 87 | bb.schedule('one', () => items.push(4)); 88 | bb.join(() => { 89 | items.push(1); 90 | bb.schedule('one', () => items.push(5)); 91 | items.push(2); 92 | }); 93 | bb.schedule('one', () => items.push(6)); 94 | items.push(3); 95 | }); 96 | assert.deepEqual(items, [0, 1, 2, 3, 4, 5, 6]); 97 | }); 98 | 99 | QUnit.test('without an onError run.join can be caught via `try`/`catch`', function(assert) { 100 | assert.expect(1); 101 | 102 | let bb = new Backburner(['errors']); 103 | 104 | assert.throws(() => { 105 | bb.join(() => { 106 | throw new Error('test error'); 107 | }); 108 | }, /test error/); 109 | }); 110 | 111 | QUnit.test('with an onError which does not rethrow, when joining existing instance, can be caught via `try`/`catch`', function(assert) { 112 | assert.expect(1); 113 | 114 | let bb = new Backburner(['errors'], { 115 | onError(error) { 116 | assert.notOk(true, 'onError should not be called as the error from .join is handled by assert.throws'); 117 | } 118 | }); 119 | 120 | bb.run(() => { 121 | assert.throws(() => { 122 | bb.join(() => { 123 | throw new Error('test error'); 124 | }); 125 | }, /test error/, 'error from within .join can be caught with try/catch'); 126 | }); 127 | }); 128 | 129 | QUnit.test('onError which does not rethrow is invoked (only once) when not joining an existing instance', function(assert) { 130 | assert.expect(1); 131 | 132 | function onError(error) { 133 | assert.equal('test error', error.message); 134 | } 135 | 136 | let bb = new Backburner(['errors'], { 137 | onError: onError 138 | }); 139 | 140 | bb.join(() => { 141 | throw new Error('test error'); 142 | }); 143 | }); 144 | 145 | QUnit.test('onError which does not rethrow is invoked (only once) when joining an existing instance', function(assert) { 146 | assert.expect(1); 147 | 148 | function onError(error) { 149 | assert.equal('test error', error.message); 150 | } 151 | 152 | let bb = new Backburner(['errors'], { 153 | onError: onError 154 | }); 155 | 156 | bb.run(() => { 157 | bb.join(() => { 158 | throw new Error('test error'); 159 | }); 160 | }); 161 | }); 162 | 163 | QUnit.test('onError which does rethrow is invoked (only once) when not joining an existing instance', function(assert) { 164 | assert.expect(2); 165 | 166 | function onError(error) { 167 | assert.equal('test error', error.message); 168 | throw error; 169 | } 170 | 171 | let bb = new Backburner(['errors'], { 172 | onError: onError 173 | }); 174 | 175 | assert.throws(() => { 176 | bb.join(() => { 177 | throw new Error('test error'); 178 | }); 179 | }, /test error/); 180 | }); 181 | 182 | QUnit.test('onError which does rethrow is invoked (only once) when joining an existing instance', function(assert) { 183 | assert.expect(2); 184 | 185 | function onError(error) { 186 | assert.equal('test error', error.message); 187 | throw error; 188 | } 189 | 190 | let bb = new Backburner(['errors'], { 191 | onError: onError 192 | }); 193 | 194 | assert.throws(() => { 195 | bb.run(() => { 196 | bb.join(() => { 197 | throw new Error('test error'); 198 | }); 199 | }); 200 | }, /test error/); 201 | }); 202 | 203 | QUnit.test('when [callback, string] args passed', function(assert) { 204 | assert.expect(2); 205 | 206 | let bb = new Backburner(['one']); 207 | let functionWasCalled = false; 208 | 209 | bb.join(function(name) { 210 | assert.equal(name, 'batman'); 211 | functionWasCalled = true; 212 | }, 'batman'); 213 | 214 | assert.ok(functionWasCalled, 'function was called'); 215 | }); 216 | -------------------------------------------------------------------------------- /lib/backburner/queue.ts: -------------------------------------------------------------------------------- 1 | import { IQueueItem } from './interfaces'; 2 | import { 3 | findItem, 4 | getOnError, 5 | getQueueItems, 6 | QUEUE_ITEM_LENGTH 7 | } from './utils'; 8 | 9 | export const enum QUEUE_STATE { 10 | Pause = 1 11 | } 12 | 13 | export default class Queue { 14 | private name: string; 15 | private globalOptions: any; 16 | private options: any; 17 | private _queueBeingFlushed: any[] = []; 18 | private targetQueues = new Map(); 19 | private index = 0; 20 | private _queue: any[] = []; 21 | 22 | constructor(name: string, options: any = {}, globalOptions: any = {}) { 23 | this.name = name; 24 | this.options = options; 25 | this.globalOptions = globalOptions; 26 | } 27 | 28 | public stackFor(index) { 29 | if (index < this._queue.length) { 30 | let entry = this._queue[(index * QUEUE_ITEM_LENGTH) + 3]; 31 | if (entry) { 32 | return entry.stack; 33 | } else { 34 | return null; 35 | } 36 | } 37 | } 38 | 39 | public consoleTaskFor(index, inQueueBeingFlushed = false) { 40 | let q = inQueueBeingFlushed ? this._queueBeingFlushed : this._queue; 41 | if (index < q.length) { 42 | return q[(index * QUEUE_ITEM_LENGTH) + 4]; 43 | } 44 | } 45 | 46 | public flush(sync?: Boolean) { 47 | let { before, after } = this.options; 48 | let target; 49 | let method; 50 | let args; 51 | let errorRecordedForStack; 52 | let consoleTask; 53 | 54 | this.targetQueues.clear(); 55 | if (this._queueBeingFlushed.length === 0) { 56 | this._queueBeingFlushed = this._queue; 57 | this._queue = []; 58 | } 59 | 60 | if (before !== undefined) { 61 | before(); 62 | } 63 | 64 | let invoke; 65 | let queueItems = this._queueBeingFlushed; 66 | if (queueItems.length > 0) { 67 | let onError = getOnError(this.globalOptions); 68 | invoke = onError ? this.invokeWithOnError : this.invoke; 69 | 70 | for (let i = this.index; i < queueItems.length; i += QUEUE_ITEM_LENGTH) { 71 | this.index += QUEUE_ITEM_LENGTH; 72 | 73 | method = queueItems[i + 1]; 74 | // method could have been nullified / canceled during flush 75 | if (method !== null) { 76 | // 77 | // ** Attention intrepid developer ** 78 | // 79 | // To find out the stack of this task when it was scheduled onto 80 | // the run loop, add the following to your app.js: 81 | // 82 | // Ember.run.backburner.DEBUG = true; // NOTE: This slows your app, don't leave it on in production. 83 | // 84 | // Once that is in place, when you are at a breakpoint and navigate 85 | // here in the stack explorer, you can look at `errorRecordedForStack.stack`, 86 | // which will be the captured stack when this job was scheduled. 87 | // 88 | // One possible long-term solution is the following Chrome issue: 89 | // https://bugs.chromium.org/p/chromium/issues/detail?id=332624 90 | // 91 | target = queueItems[i]; 92 | args = queueItems[i + 2]; 93 | errorRecordedForStack = queueItems[i + 3]; // Debugging assistance 94 | consoleTask = queueItems[i + 4]; 95 | if(consoleTask){ 96 | consoleTask.run(invoke.bind(this, target, method, args, onError, errorRecordedForStack)) 97 | }else{ 98 | invoke(target, method, args, onError, errorRecordedForStack) 99 | } 100 | } 101 | 102 | if (this.index !== this._queueBeingFlushed.length && 103 | this.globalOptions.mustYield && this.globalOptions.mustYield()) { 104 | return QUEUE_STATE.Pause; 105 | } 106 | } 107 | } 108 | 109 | if (after !== undefined) { 110 | after(); 111 | } 112 | 113 | this._queueBeingFlushed.length = 0; 114 | this.index = 0; 115 | if (sync !== false && this._queue.length > 0) { 116 | // check if new items have been added 117 | this.flush(true); 118 | } 119 | } 120 | 121 | public hasWork() { 122 | return this._queueBeingFlushed.length > 0 || this._queue.length > 0; 123 | } 124 | 125 | public cancel({ target, method }) { 126 | let queue = this._queue; 127 | let targetQueueMap = this.targetQueues.get(target); 128 | 129 | if (targetQueueMap !== undefined) { 130 | targetQueueMap.delete(method); 131 | } 132 | 133 | let index = findItem(target, method, queue); 134 | 135 | if (index > -1) { 136 | queue[index + 1] = null; 137 | return true; 138 | } 139 | 140 | // if not found in current queue 141 | // could be in the queue that is being flushed 142 | queue = this._queueBeingFlushed; 143 | 144 | index = findItem(target, method, queue); 145 | if (index > -1) { 146 | queue[index + 1] = null; 147 | return true; 148 | } 149 | 150 | return false; 151 | } 152 | 153 | public push(target, method, args, stack, consoleTask): { queue: Queue, target, method } { 154 | this._queue.push(target, method, args, stack, consoleTask); 155 | 156 | return { 157 | queue: this, 158 | target, 159 | method 160 | }; 161 | } 162 | 163 | public pushUnique(target, method, args, stack, consoleTask): { queue: Queue, target, method } { 164 | let localQueueMap = this.targetQueues.get(target); 165 | 166 | if (localQueueMap === undefined) { 167 | localQueueMap = new Map(); 168 | this.targetQueues.set(target, localQueueMap); 169 | } 170 | 171 | let index = localQueueMap.get(method); 172 | if (index === undefined) { 173 | let queueIndex = this._queue.push(target, method, args, stack, consoleTask) - QUEUE_ITEM_LENGTH; 174 | localQueueMap.set(method, queueIndex); 175 | } else { 176 | let queue = this._queue; 177 | queue[index + 2] = args; // replace args 178 | queue[index + 3] = stack; // replace stack 179 | queue[index + 3] = consoleTask; // replace consoleTask 180 | } 181 | 182 | return { 183 | queue: this, 184 | target, 185 | method 186 | }; 187 | } 188 | 189 | public _getDebugInfo(debugEnabled: boolean): IQueueItem[] | undefined { 190 | if (debugEnabled) { 191 | let debugInfo: IQueueItem[] = getQueueItems(this._queue, QUEUE_ITEM_LENGTH); 192 | 193 | return debugInfo; 194 | } 195 | 196 | return undefined; 197 | } 198 | 199 | private invoke(target, method, args /*, onError, errorRecordedForStack */) { 200 | if (args === undefined) { 201 | method.call(target); 202 | } else { 203 | method.apply(target, args); 204 | } 205 | } 206 | 207 | private invokeWithOnError(target, method, args, onError, errorRecordedForStack) { 208 | try { 209 | if (args === undefined) { 210 | method.call(target); 211 | } else { 212 | method.apply(target, args); 213 | } 214 | } catch (error) { 215 | onError(error, errorRecordedForStack); 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /tests/debug-info-test.ts: -------------------------------------------------------------------------------- 1 | import Backburner from 'backburner'; 2 | import MockStableError, { 3 | overrideError, 4 | pushStackTrace, 5 | resetError, 6 | } from './utils/mock-stable-error'; 7 | 8 | QUnit.module('tests/debug-info', { 9 | beforeEach: function() { 10 | // @ts-ignore 11 | overrideError(MockStableError); 12 | }, 13 | 14 | afterEach: function() { 15 | resetError(); 16 | } 17 | }); 18 | 19 | QUnit.test('getDebugInfo returns undefined when DEBUG = false', function(assert) { 20 | assert.expect(1); 21 | 22 | let debugInfo; 23 | let bb = new Backburner(['one']); 24 | 25 | bb.run(function() { 26 | debugInfo = bb.getDebugInfo(); 27 | 28 | assert.equal(debugInfo, undefined, 'DebugInfo is undefined when DEBUG = false'); 29 | }); 30 | }); 31 | 32 | QUnit.test('getDebugInfo returns debugInfo using later when DEBUG = true', function(assert) { 33 | assert.expect(1); 34 | 35 | let debugInfo; 36 | let target1 = { one: true }; 37 | let target2 = { two: true }; 38 | let method = () => {}; 39 | let arg1 = 1; 40 | let arg2 = 2; 41 | let twoStack = pushStackTrace('Two stack'); 42 | let oneStack = pushStackTrace('One stack'); 43 | let bb = new Backburner(['one']); 44 | 45 | bb.DEBUG = true; 46 | 47 | bb.run(function() { 48 | bb.later(target1, method, arg1, 1000); 49 | bb.later(target2, method, arg1, arg2, 1000); 50 | 51 | debugInfo = bb.getDebugInfo(); 52 | 53 | resetError(); 54 | 55 | assert.deepEqual(debugInfo.timers, 56 | [ 57 | { 58 | args: [arg1], 59 | method, 60 | stack: oneStack, 61 | target: target1 62 | }, 63 | { 64 | args: [arg1, arg2], 65 | method, 66 | stack: twoStack, 67 | target: target2 68 | } 69 | ] 70 | , 'debugInfo is output'); 71 | }); 72 | }); 73 | 74 | QUnit.test('getDebugInfo returns debugInfo using throttle when DEBUG = true', function(assert) { 75 | assert.expect(1); 76 | 77 | let debugInfo; 78 | let target1 = { one: true }; 79 | let target2 = { two: true }; 80 | let method = () => {}; 81 | let arg1 = 1; 82 | let arg2 = 2; 83 | let twoStack = pushStackTrace('Two stack'); 84 | let oneStack = pushStackTrace('One stack'); 85 | let bb = new Backburner(['one']); 86 | 87 | bb.DEBUG = true; 88 | 89 | bb.run(function() { 90 | bb.throttle(target1, method, arg1, 1000, false); 91 | bb.throttle(target2, method, arg1, arg2, 1000, false); 92 | 93 | debugInfo = bb.getDebugInfo(); 94 | 95 | resetError(); 96 | 97 | assert.deepEqual(debugInfo.timers, 98 | [ 99 | { 100 | args: [arg1], 101 | method, 102 | stack: oneStack, 103 | target: target1 104 | }, 105 | { 106 | args: [arg1, arg2], 107 | method, 108 | stack: twoStack, 109 | target: target2 110 | } 111 | ] 112 | , 'debugInfo is output'); 113 | }); 114 | }); 115 | 116 | QUnit.test('getDebugInfo returns debugInfo using debounce when DEBUG = true', function(assert) { 117 | assert.expect(1); 118 | 119 | let debugInfo; 120 | let target1 = { one: true }; 121 | let target2 = { two: true }; 122 | let method = () => {}; 123 | let arg1 = 1; 124 | let arg2 = 2; 125 | let twoStack = pushStackTrace('Two stack'); 126 | let oneStack = pushStackTrace('One stack'); 127 | let bb = new Backburner(['one']); 128 | 129 | bb.DEBUG = true; 130 | 131 | bb.run(function() { 132 | bb.debounce(target1, method, arg1, 1000); 133 | bb.debounce(target2, method, arg1, arg2, 1000); 134 | 135 | debugInfo = bb.getDebugInfo(); 136 | 137 | resetError(); 138 | 139 | assert.deepEqual(debugInfo.timers, 140 | [ 141 | { 142 | args: [arg1], 143 | method, 144 | stack: oneStack, 145 | target: target1 146 | }, 147 | { 148 | args: [arg1, arg2], 149 | method, 150 | stack: twoStack, 151 | target: target2 152 | } 153 | ] 154 | , 'debugInfo is output'); 155 | }); 156 | }); 157 | 158 | QUnit.test('getDebugInfo returns debugInfo using when DEBUG = true', function(assert) { 159 | assert.expect(1); 160 | 161 | let debugInfo; 162 | let target1 = { one: true }; 163 | let target2 = { two: true }; 164 | let method = () => {}; 165 | let arg1 = 1; 166 | let arg2 = 2; 167 | let twoStack = pushStackTrace('Two stack'); 168 | let oneStack = pushStackTrace('One stack'); 169 | let bb = new Backburner(['one', 'two']); 170 | 171 | bb.DEBUG = true; 172 | 173 | bb.run(function() { 174 | bb.schedule('one', target1, method, arg1); 175 | bb.schedule('two', target2, method, arg1, arg2); 176 | 177 | debugInfo = bb.getDebugInfo(); 178 | 179 | resetError(); 180 | 181 | assert.deepEqual(debugInfo.instanceStack, 182 | [ 183 | { 184 | one: [ 185 | { 186 | args: [arg1], 187 | method, 188 | stack: oneStack, 189 | target: target1 190 | } 191 | ], 192 | two: [ 193 | { 194 | args: [arg1, arg2], 195 | method, 196 | stack: twoStack, 197 | target: target2 198 | } 199 | ] 200 | } 201 | ] 202 | , 'debugInfo is output'); 203 | }); 204 | }); 205 | 206 | QUnit.test('getDebugInfo returns debugInfo when DEBUG = true in nested run', function(assert) { 207 | assert.expect(1); 208 | 209 | let debugInfo; 210 | let method = () => {}; 211 | let twoStack = pushStackTrace('Two stack'); 212 | let oneStack = pushStackTrace('One stack'); 213 | let fourStack = pushStackTrace('Four stack'); 214 | let threeStack = pushStackTrace('Three stack'); 215 | let bb = new Backburner(['one', 'two', 'three', 'four']); 216 | 217 | bb.DEBUG = true; 218 | 219 | bb.run(function() { 220 | bb.schedule('one', method); 221 | bb.schedule('two', method); 222 | 223 | bb.run(function() { 224 | bb.schedule('three', method); 225 | bb.schedule('four', method); 226 | 227 | debugInfo = bb.getDebugInfo(); 228 | 229 | resetError(); 230 | 231 | assert.deepEqual(debugInfo.instanceStack, 232 | [ 233 | { 234 | four: [ 235 | { 236 | args: undefined, 237 | method, 238 | stack: fourStack, 239 | target: null 240 | } 241 | ], 242 | one: [], 243 | three: [ 244 | { 245 | args: undefined, 246 | method, 247 | stack: threeStack, 248 | target: null 249 | } 250 | ], 251 | two: [] 252 | }, 253 | { 254 | four: [], 255 | one: [ 256 | { 257 | args: undefined, 258 | method, 259 | stack: oneStack, 260 | target: null 261 | } 262 | ], 263 | three: [], 264 | two: [ 265 | { 266 | args: undefined, 267 | method, 268 | stack: twoStack, 269 | target: null 270 | } 271 | ] 272 | } 273 | ] 274 | , 'debugInfo is output'); 275 | }); 276 | }); 277 | }); 278 | 279 | QUnit.test('autorun', function(assert) { 280 | let done = assert.async(); 281 | let bb = new Backburner(['one']); 282 | let autorunStack = pushStackTrace('Autorun stack'); 283 | pushStackTrace('Schedule stack'); 284 | 285 | bb.DEBUG = true; 286 | 287 | bb.schedule('one', null, () => { 288 | setTimeout(() => { 289 | assert.equal(bb.getDebugInfo()!.autorun, null); 290 | done(); 291 | }); 292 | }); 293 | 294 | assert.equal(bb.getDebugInfo()!.autorun!.stack, autorunStack); 295 | }); 296 | -------------------------------------------------------------------------------- /tests/defer-test.ts: -------------------------------------------------------------------------------- 1 | import Backburner from 'backburner'; 2 | let originalDateValueOf = Date.prototype.valueOf; 3 | 4 | QUnit.module('tests/defer', { 5 | afterEach() { 6 | Date.prototype.valueOf = originalDateValueOf; 7 | } 8 | }); 9 | 10 | QUnit.test('when passed a function', function(assert) { 11 | assert.expect(1); 12 | 13 | let bb = new Backburner(['one']); 14 | let functionWasCalled = false; 15 | 16 | bb.run(() => { 17 | bb.schedule('one', () => functionWasCalled = true); 18 | }); 19 | 20 | assert.ok(functionWasCalled, 'function was called'); 21 | }); 22 | 23 | QUnit.test('when passed a target and method', function(assert) { 24 | assert.expect(2); 25 | 26 | let bb = new Backburner(['one']); 27 | let functionWasCalled = false; 28 | 29 | bb.run(() => { 30 | bb.schedule('one', { zomg: 'hi' }, function() { 31 | assert.equal(this.zomg, 'hi', 'the target was properly set'); 32 | functionWasCalled = true; 33 | }); 34 | }); 35 | 36 | assert.ok(functionWasCalled, 'function was called'); 37 | }); 38 | 39 | QUnit.test('when [queueName, callback, string] args passed', function(assert) { 40 | assert.expect(2); 41 | 42 | let bb = new Backburner(['one']); 43 | let functionWasCalled = false; 44 | 45 | bb.run(() => { 46 | bb.schedule('one', function(name) { 47 | assert.equal(name, 'batman'); 48 | functionWasCalled = true; 49 | }, 'batman'); 50 | }); 51 | 52 | assert.ok(functionWasCalled, 'function was called'); 53 | }); 54 | 55 | QUnit.test('when passed a target and method name', function(assert) { 56 | assert.expect(2); 57 | 58 | let bb = new Backburner(['one']); 59 | let functionWasCalled = false; 60 | let targetObject = { 61 | zomg: 'hi', 62 | checkFunction() { 63 | assert.equal(this.zomg, 'hi', 'the target was properly set'); 64 | functionWasCalled = true; 65 | } 66 | }; 67 | 68 | bb.run(() => bb.schedule('one', targetObject, 'checkFunction')); 69 | 70 | assert.ok(functionWasCalled, 'function was called'); 71 | }); 72 | 73 | QUnit.test('throws when passed a null method', function(assert) { 74 | assert.expect(1); 75 | 76 | function onError(error) { 77 | assert.equal('You attempted to schedule an action in a queue (deferErrors) for a method that doesn\'t exist', error.message); 78 | } 79 | 80 | let bb = new Backburner(['deferErrors'], { 81 | onError 82 | }); 83 | 84 | bb.run(() => bb.schedule('deferErrors', { zomg: 'hi' }, null)); 85 | }); 86 | 87 | QUnit.test('throws when passed an undefined method', function(assert) { 88 | assert.expect(1); 89 | 90 | function onError(error) { 91 | assert.equal('You attempted to schedule an action in a queue (deferErrors) for a method that doesn\'t exist', error.message); 92 | } 93 | 94 | let bb = new Backburner(['deferErrors'], { 95 | onError 96 | }); 97 | 98 | bb.run(() => bb.schedule('deferErrors', { zomg: 'hi' }, undefined)); 99 | }); 100 | 101 | QUnit.test('throws when passed an method name that does not exists on the target', function(assert) { 102 | assert.expect(1); 103 | 104 | function onError(error) { 105 | assert.equal('You attempted to schedule an action in a queue (deferErrors) for a method that doesn\'t exist', error.message); 106 | } 107 | 108 | let bb = new Backburner(['deferErrors'], { 109 | onError 110 | }); 111 | 112 | bb.run(() => bb.schedule('deferErrors', { zomg: 'hi' }, 'checkFunction')); 113 | }); 114 | 115 | QUnit.test('when passed a target, method, and arguments', function(assert) { 116 | assert.expect(5); 117 | 118 | let bb = new Backburner(['one']); 119 | let functionWasCalled = false; 120 | 121 | bb.run(() => { 122 | bb.schedule('one', { zomg: 'hi' }, function(a, b, c) { 123 | assert.equal(this.zomg, 'hi', 'the target was properly set'); 124 | assert.equal(a, 1, 'the first arguments was passed in'); 125 | assert.equal(b, 2, 'the second arguments was passed in'); 126 | assert.equal(c, 3, 'the third arguments was passed in'); 127 | functionWasCalled = true; 128 | }, 1, 2, 3); 129 | }); 130 | 131 | assert.ok(functionWasCalled, 'function was called'); 132 | }); 133 | 134 | QUnit.test('when passed same function twice', function(assert) { 135 | assert.expect(1); 136 | 137 | let bb = new Backburner(['one']); 138 | let i = 0; 139 | 140 | function deferMethod() { 141 | i++; 142 | } 143 | 144 | bb.run(() => { 145 | bb.schedule('one', deferMethod); 146 | bb.schedule('one', deferMethod); 147 | }); 148 | 149 | assert.equal(i, 2, 'function was called twice'); 150 | }); 151 | 152 | QUnit.test('when passed same function twice with arguments', function(assert) { 153 | assert.expect(2); 154 | 155 | let bb = new Backburner(['one']); 156 | let i = 0; 157 | let argObj = {first : 1}; 158 | 159 | function deferMethod() { 160 | assert.equal(this['first'], 1, 'the target property was set'); 161 | } 162 | 163 | bb.run(() => { 164 | bb.schedule('one', argObj, deferMethod); 165 | bb.schedule('one', argObj, deferMethod); 166 | }); 167 | }); 168 | 169 | QUnit.test('when passed same function twice with same arguments and same target', function(assert) { 170 | assert.expect(7); 171 | 172 | let bb = new Backburner(['one']); 173 | let i = 0; 174 | 175 | function deferMethod(a, b) { 176 | i++; 177 | assert.equal(a, 1, 'First argument is set twice'); 178 | assert.equal(b, 2, 'Second argument is set twice'); 179 | assert.equal(this['first'], 1, 'the target property was set'); 180 | } 181 | 182 | let argObj = { first: 1 }; 183 | 184 | bb.run(() => { 185 | bb.schedule('one', argObj, deferMethod, 1, 2); 186 | bb.schedule('one', argObj, deferMethod, 1, 2); 187 | }); 188 | 189 | assert.equal(i, 2, 'function was called twice'); 190 | }); 191 | 192 | QUnit.test('when passed same function twice with same target and different arguments', function(assert) { 193 | assert.expect(7); 194 | 195 | let bb = new Backburner(['one']); 196 | let i = 0; 197 | 198 | function deferMethod(a, b) { 199 | i++; 200 | if (i === 1) { 201 | assert.equal(a, 1, 'First argument set during first call'); 202 | } else { 203 | assert.equal(a, 3, 'First argument set during second call'); 204 | } 205 | assert.equal(b, 2, 'Second argument remains same'); 206 | assert.equal(this['first'], 1, 'the target property was set'); 207 | } 208 | 209 | let argObj = { first: 1 }; 210 | 211 | bb.run(() => { 212 | bb.schedule('one', argObj, deferMethod, 1, 2); 213 | bb.schedule('one', argObj, deferMethod, 3, 2); 214 | }); 215 | 216 | assert.equal(i, 2, 'function was called twice'); 217 | }); 218 | 219 | QUnit.test('when passed same function twice with different target and different arguments', function(assert) { 220 | assert.expect(7); 221 | 222 | let bb = new Backburner(['one']); 223 | let i = 0; 224 | 225 | function deferMethod(a, b) { 226 | i++; 227 | if (i === 1) { 228 | assert.equal(a, 1, 'First argument set during first call'); 229 | } else { 230 | assert.equal(a, 3, 'First argument set during second call'); 231 | } 232 | assert.equal(b, 2, 'Second argument remains same'); 233 | assert.equal(this['first'], 1, 'the target property was set'); 234 | } 235 | 236 | let argObj = {first: 1}; 237 | 238 | bb.run(() => { 239 | bb.schedule('one', { first: 1 }, deferMethod, 1, 2); 240 | bb.schedule('one', { first: 1 }, deferMethod, 3, 2); 241 | }); 242 | 243 | assert.equal(i, 2, 'function was called twice'); 244 | }); 245 | 246 | QUnit.test('onError', function(assert) { 247 | assert.expect(1); 248 | 249 | function onError(error) { 250 | assert.equal('QUnit.test error', error.message); 251 | } 252 | 253 | let bb = new Backburner(['errors'], { 254 | onError 255 | }); 256 | 257 | bb.run(() => { 258 | bb.schedule('errors', () => { 259 | throw new Error('QUnit.test error'); 260 | }); 261 | }); 262 | }); 263 | -------------------------------------------------------------------------------- /tests/cancel-test.ts: -------------------------------------------------------------------------------- 1 | 2 | import Backburner from 'backburner'; 3 | 4 | QUnit.module('tests/cancel'); 5 | 6 | QUnit.test('scheduleOnce', function(assert) { 7 | assert.expect(3); 8 | 9 | let bb = new Backburner(['one']); 10 | let functionWasCalled = false; 11 | 12 | bb.run(() => { 13 | let timer = bb.scheduleOnce('one', () => functionWasCalled = true); 14 | 15 | assert.ok(timer, 'Timer object was returned'); 16 | assert.ok(bb.cancel(timer), 'Cancel returned true'); 17 | assert.ok(!functionWasCalled, 'function was not called'); 18 | }); 19 | }); 20 | 21 | QUnit.test('cancelling does not affect future scheduleOnce calls', function(assert) { 22 | assert.expect(5); 23 | 24 | let bb = new Backburner(['queueName']); 25 | const f1Calls: string[] = []; 26 | const f2Calls: string[] = []; 27 | const f3Calls: string[] = []; 28 | const f1 = (arg: string) => f1Calls.push(arg); 29 | const f2 = (arg: string) => f2Calls.push(arg); 30 | const f3 = (arg: string) => f3Calls.push(arg); 31 | 32 | bb.run(() => { 33 | const toCancel = bb.scheduleOnce('queueName', null, f1, 'f1 cancelled schedule'); 34 | bb.scheduleOnce('queueName', null, f2, 'f2 first schedule'); 35 | bb.scheduleOnce('queueName', null, f3, 'f3 first schedule'); 36 | bb.cancel(toCancel); 37 | bb.scheduleOnce('queueName', null, f2, 'f2 second schedule'); 38 | }); 39 | 40 | assert.equal(f1Calls.length, 0, 'f1 was not called') 41 | assert.equal(f2Calls.length, 1, 'f2 was called once') 42 | assert.equal(f3Calls.length, 1, 'f3 was called once') 43 | assert.deepEqual(f2Calls, ['f2 second schedule'], 'f2 received the correct argument') 44 | assert.deepEqual(f3Calls, ['f3 first schedule'], 'f3 received the correct argument') 45 | }); 46 | 47 | QUnit.test('setTimeout', function(assert) { 48 | assert.expect(5); 49 | let done = assert.async(); 50 | 51 | let called = false; 52 | let bb = new Backburner(['one'], { 53 | onBegin() { 54 | called = true; 55 | } 56 | }); 57 | 58 | let functionWasCalled = false; 59 | let timer = bb.later(() => functionWasCalled = true); 60 | 61 | assert.ok(timer, 'Timer object was returned'); 62 | assert.ok(bb.cancel(timer), 'Cancel returned true'); 63 | assert.ok(!called, 'onBegin was not called'); 64 | 65 | setTimeout(() => { 66 | assert.ok(!functionWasCalled, 'function was not called'); 67 | assert.ok(!called, 'onBegin was not called'); 68 | done(); 69 | }, 0); 70 | }); 71 | 72 | QUnit.test('setTimeout with multiple pending', function(assert) { 73 | assert.expect(7); 74 | 75 | let done = assert.async(); 76 | let called = false; 77 | let bb = new Backburner(['one'], { 78 | onBegin() { 79 | called = true; 80 | } 81 | }); 82 | let function1WasCalled = false; 83 | let function2WasCalled = false; 84 | 85 | let timer1 = bb.later(() => function1WasCalled = true); 86 | let timer2 = bb.later(() => function2WasCalled = true); 87 | 88 | assert.ok(timer1, 'Timer object 2 was returned'); 89 | assert.ok(bb.cancel(timer1), 'Cancel for timer 1 returned true'); 90 | assert.ok(timer2, 'Timer object 2 was returned'); 91 | assert.ok(!called, 'onBegin was not called'); 92 | 93 | setTimeout(() => { 94 | assert.ok(!function1WasCalled, 'function 1 was not called'); 95 | assert.ok(function2WasCalled, 'function 2 was called'); 96 | assert.ok(called, 'onBegin was called'); 97 | 98 | done(); 99 | }, 10); 100 | }); 101 | 102 | QUnit.test('setTimeout and creating a new later', function(assert) { 103 | assert.expect(7); 104 | let done = assert.async(); 105 | let called = false; 106 | let bb = new Backburner(['one'], { 107 | onBegin() { 108 | called = true; 109 | } 110 | }); 111 | let function1WasCalled = false; 112 | let function2WasCalled = false; 113 | 114 | let timer1 = bb.later(() => function1WasCalled = true, 0); 115 | 116 | assert.ok(timer1, 'Timer object 2 was returned'); 117 | assert.ok(bb.cancel(timer1), 'Cancel for timer 1 returned true'); 118 | 119 | let timer2 = bb.later(() => function2WasCalled = true, 1); 120 | 121 | assert.ok(timer2, 'Timer object 2 was returned'); 122 | assert.ok(!called, 'onBegin was not called'); 123 | 124 | setTimeout(() => { 125 | assert.ok(!function1WasCalled, 'function 1 was not called'); 126 | assert.ok(function2WasCalled, 'function 2 was called'); 127 | assert.ok(called, 'onBegin was called'); 128 | done(); 129 | }, 50); 130 | }); 131 | 132 | QUnit.test('cancelTimers', function(assert) { 133 | assert.expect(8); 134 | let done = assert.async(); 135 | 136 | let bb = new Backburner(['one']); 137 | let laterWasCalled = false; 138 | let debounceWasCalled = false; 139 | let throttleWasCalled = false; 140 | 141 | let timer1 = bb.later(() => laterWasCalled = true, 0); 142 | let timer2 = bb.debounce(() => debounceWasCalled = true, 0); 143 | let timer3 = bb.throttle(() => throttleWasCalled = true, 0, false); 144 | 145 | assert.ok(timer1, 'Timer object was returned'); 146 | assert.ok(timer2, 'Timer object was returned'); 147 | assert.ok(timer3, 'Timer object was returned'); 148 | assert.ok(bb.hasTimers(), 'bb has scheduled timer'); 149 | 150 | bb.cancelTimers(); 151 | 152 | setTimeout(function() { 153 | assert.ok(!bb.hasTimers(), 'bb has no scheduled timer'); 154 | assert.ok(!laterWasCalled, 'later function was not called'); 155 | assert.ok(!debounceWasCalled, 'debounce function was not called'); 156 | assert.ok(!throttleWasCalled, 'throttle function was not called'); 157 | done(); 158 | }, 100); 159 | }); 160 | 161 | QUnit.test('cancel during flush', function(assert) { 162 | assert.expect(1); 163 | 164 | let bb = new Backburner(['one']); 165 | let functionWasCalled = false; 166 | 167 | bb.run(() => { 168 | let timer1 = bb.scheduleOnce('one', () => bb.cancel(timer2)); 169 | let timer2 = bb.scheduleOnce('one', () => functionWasCalled = true); 170 | }); 171 | 172 | assert.ok(!functionWasCalled, 'function was not called'); 173 | }); 174 | 175 | QUnit.test('with target', function(assert) { 176 | assert.expect(3); 177 | 178 | let obj = { 179 | ___FOO___: 1 180 | }; 181 | 182 | let bb = new Backburner(['action']); 183 | 184 | let wasCalled = 0; 185 | 186 | function fn() { 187 | wasCalled++; 188 | } 189 | 190 | bb.run(() => { 191 | let timer = bb.scheduleOnce('action', obj, fn); 192 | 193 | assert.equal(wasCalled, 0); 194 | 195 | bb.cancel(timer); 196 | bb.scheduleOnce('action', obj, fn); 197 | 198 | assert.equal(wasCalled, 0); 199 | }); 200 | 201 | assert.equal(wasCalled, 1); 202 | }); 203 | 204 | QUnit.test('no target', function(assert) { 205 | assert.expect(3); 206 | 207 | let bb = new Backburner(['action']); 208 | 209 | let wasCalled = 0; 210 | 211 | function fn() { 212 | wasCalled++; 213 | } 214 | 215 | bb.run(() => { 216 | let timer = bb.scheduleOnce('action', fn); 217 | 218 | assert.equal(wasCalled, 0); 219 | 220 | bb.cancel(timer); 221 | bb.scheduleOnce('action', fn); 222 | 223 | assert.equal(wasCalled, 0); 224 | }); 225 | 226 | assert.equal(wasCalled, 1); 227 | }); 228 | 229 | QUnit.test('cancel always returns boolean', function(assert) { 230 | let bb = new Backburner(['one']); 231 | 232 | bb.run(function() { 233 | let timer1 = bb.schedule('one', null, function() {}); 234 | assert.equal(bb.cancel(timer1), true); 235 | assert.equal(bb.cancel(timer1), false); 236 | assert.equal(bb.cancel(timer1), false); 237 | 238 | let timer2 = bb.later(function() {}, 10); 239 | assert.equal(bb.cancel(timer2), true); 240 | assert.equal(bb.cancel(timer2), false); 241 | assert.equal(bb.cancel(timer2), false); 242 | 243 | let timer3 = bb.debounce(function() {}, 10); 244 | assert.equal(bb.cancel(timer3), true); 245 | assert.equal(bb.cancel(timer3), false); 246 | assert.equal(bb.cancel(timer3), false); 247 | 248 | assert.equal(bb.cancel(undefined), false); 249 | assert.equal(bb.cancel(null), false); 250 | assert.equal(bb.cancel({}), false); 251 | assert.equal(bb.cancel([]), false); 252 | assert.equal(bb.cancel(42), false); 253 | assert.equal(bb.cancel('42'), false); 254 | }); 255 | }); 256 | -------------------------------------------------------------------------------- /tests/autorun-test.ts: -------------------------------------------------------------------------------- 1 | import Backburner from 'backburner'; 2 | import lolex from 'lolex'; 3 | 4 | // used to ensure tests for fake timers can reliably use native setTimeout 5 | const SET_TIMEOUT = setTimeout; 6 | let fakeClock; 7 | 8 | function escapeCurrentMicrotaskQueue() { 9 | return new Promise((resolve) => { 10 | // this ensures that we have been to the end of the current 11 | // events microtask queue 12 | setTimeout(resolve, 0); 13 | }); 14 | } 15 | 16 | QUnit.module('tests/autorun', { 17 | afterEach() { 18 | if (fakeClock) { 19 | fakeClock.uninstall(); 20 | } 21 | } 22 | }); 23 | 24 | QUnit.test('autorun', function(assert) { 25 | let done = assert.async(); 26 | let bb = new Backburner(['zomg']); 27 | let step = 0; 28 | 29 | assert.ok(!bb.currentInstance, 'The DeferredActionQueues object is lazily instaniated'); 30 | assert.equal(step++, 0); 31 | 32 | bb.schedule('zomg', null, () => { 33 | assert.equal(step++, 2); 34 | setTimeout(() => { 35 | assert.ok(!bb.hasTimers(), 'The all timers are cleared'); 36 | done(); 37 | }); 38 | }); 39 | 40 | assert.ok(bb.currentInstance, 'The DeferredActionQueues object exists'); 41 | assert.equal(step++, 1); 42 | }); 43 | 44 | QUnit.test('autorun (joins next run if not yet flushed)', function(assert) { 45 | let bb = new Backburner(['zomg']); 46 | let order = -1; 47 | 48 | let tasks = { 49 | one: { count: 0, order: -1 }, 50 | two: { count: 0, order: -1 } 51 | }; 52 | 53 | bb.schedule('zomg', null, () => { 54 | tasks.one.count++; 55 | tasks.one.order = ++order; 56 | }); 57 | 58 | assert.deepEqual(tasks, { 59 | one: { count: 0, order: -1 }, 60 | two: { count: 0, order: -1 } 61 | }); 62 | 63 | bb.run(() => { 64 | bb.schedule('zomg', null, () => { 65 | tasks.two.count++; 66 | tasks.two.order = ++order; 67 | }); 68 | 69 | assert.deepEqual(tasks, { 70 | one: { count: 0, order: -1 }, 71 | two: { count: 0, order: -1 } 72 | }); 73 | }); 74 | 75 | assert.deepEqual(tasks, { 76 | one: { count: 1, order: 0 }, 77 | two: { count: 1, order: 1 } 78 | }); 79 | }); 80 | 81 | QUnit.test('autorun completes before items scheduled by later (via microtasks)', function(assert) { 82 | let done = assert.async(); 83 | let bb = new Backburner(['first', 'second']); 84 | let order = new Array(); 85 | 86 | // this later will be scheduled into the `first` queue when 87 | // its timer is up 88 | bb.later(() => { 89 | order.push('second - later'); 90 | }, 0); 91 | 92 | // scheduling this into the second queue so that we can confirm this _still_ 93 | // runs first (due to autorun resolving before scheduled timer) 94 | bb.schedule('second', null, () => { 95 | order.push('first - scheduled'); 96 | }); 97 | 98 | setTimeout(() => { 99 | assert.deepEqual(order, ['first - scheduled', 'second - later']); 100 | done(); 101 | }, 20); 102 | }); 103 | 104 | QUnit.test('can be canceled (private API)', function(assert) { 105 | assert.expect(0); 106 | 107 | let done = assert.async(); 108 | let bb = new Backburner(['zomg']); 109 | 110 | bb.schedule('zomg', null, () => { 111 | assert.notOk(true, 'should not flush'); 112 | }); 113 | 114 | bb['_cancelAutorun'](); 115 | 116 | setTimeout(done, 10); 117 | }); 118 | 119 | QUnit.test('autorun interleaved with microtasks do not get dropped [GH#332]', function(assert) { 120 | let done = assert.async(); 121 | let actual: string[] = []; 122 | let bb = new Backburner(['actions', 'render']); 123 | 124 | bb.schedule('render', function() { 125 | actual.push('first'); 126 | bb.schedule('actions', () => { 127 | actual.push('action1'); 128 | }); 129 | 130 | Promise.resolve().then(() => { 131 | actual.push('second'); 132 | bb.schedule('actions', () => { 133 | actual.push('action2'); 134 | }); 135 | 136 | return Promise.resolve().then(() => { 137 | actual.push('third'); 138 | 139 | bb.schedule('actions', () => { 140 | actual.push('action3'); 141 | }); 142 | }); 143 | }); 144 | }); 145 | 146 | setTimeout(function() { 147 | assert.deepEqual(actual, ['first', 'action1', 'second', 'action2', 'third', 'action3']); 148 | 149 | done(); 150 | }); 151 | }); 152 | 153 | QUnit.test('autorun functions even when using fake timers', function(assert) { 154 | let done = assert.async(); 155 | let bb = new Backburner(['zomg']); 156 | let step = 0; 157 | 158 | assert.ok(!bb.currentInstance, 'The DeferredActionQueues object is lazily instaniated'); 159 | assert.equal(step++, 0); 160 | 161 | fakeClock = lolex.install(); 162 | bb.schedule('zomg', null, () => { 163 | assert.equal(step++, 2); 164 | SET_TIMEOUT(() => { 165 | assert.ok(!bb.hasTimers(), 'The all timers are cleared'); 166 | done(); 167 | }); 168 | }); 169 | 170 | assert.ok(bb.currentInstance, 'The DeferredActionQueues object exists'); 171 | assert.equal(step++, 1); 172 | }); 173 | 174 | QUnit.test('customizing flushing per queue via flush', function(assert) { 175 | assert.step('start'); 176 | 177 | let deferredFlush; 178 | 179 | let bb = new Backburner( 180 | [ 181 | 'zomg', 182 | 'render', 183 | 'afterRender' 184 | ], 185 | { 186 | flush(queueName, flush) { 187 | if (queueName === 'render') { 188 | deferredFlush = flush; 189 | } else { 190 | flush(); 191 | } 192 | } 193 | } 194 | ); 195 | 196 | bb.schedule('zomg', null, () => { 197 | assert.step('running zomg'); 198 | }); 199 | 200 | bb.schedule('render', null, () => { 201 | assert.step('running render'); 202 | }); 203 | 204 | bb.schedule('afterRender', null, () => { 205 | assert.step('running afterRender'); 206 | }); 207 | 208 | return escapeCurrentMicrotaskQueue() 209 | .then(() => { 210 | deferredFlush(); 211 | }) 212 | .then(escapeCurrentMicrotaskQueue) 213 | .then(() => { 214 | assert.verifySteps([ 215 | 'start', 216 | 'running zomg', 217 | 'running render', 218 | 'running afterRender', 219 | ]); 220 | }); 221 | }); 222 | 223 | QUnit.test('customized flushing - precedence is rechecked upon each flush', function(assert) { 224 | assert.step('start'); 225 | 226 | let deferredFlush; 227 | 228 | let bb = new Backburner( 229 | [ 230 | 'zomg', 231 | 'render', 232 | 'afterRender' 233 | ], 234 | { 235 | flush(queueName, flush) { 236 | if (deferredFlush === undefined && queueName === 'render') { 237 | deferredFlush = flush; 238 | } else { 239 | flush(); 240 | } 241 | } 242 | } 243 | ); 244 | 245 | bb.schedule('zomg', null, () => { 246 | assert.step('running zomg'); 247 | }); 248 | 249 | bb.schedule('render', null, () => { 250 | assert.step('running render'); 251 | }); 252 | 253 | bb.schedule('afterRender', null, () => { 254 | assert.step('running afterRender'); 255 | }); 256 | 257 | return escapeCurrentMicrotaskQueue() 258 | .then(() => { 259 | bb.schedule('zomg', null, () => { 260 | assert.step('running zomg 2'); 261 | }); 262 | 263 | deferredFlush(); 264 | }) 265 | .then(escapeCurrentMicrotaskQueue) 266 | .then(() => { 267 | assert.verifySteps([ 268 | 'start', 269 | 'running zomg', 270 | 'running zomg 2', 271 | 'running render', 272 | 'running afterRender', 273 | ]); 274 | }); 275 | }); 276 | 277 | QUnit.test('customizing flushing per queue via flush - with forced run', function(assert) { 278 | assert.step('start'); 279 | 280 | let deferredFlush; 281 | 282 | let bb = new Backburner( 283 | [ 284 | 'zomg', 285 | 'render', 286 | 'afterRender' 287 | ], 288 | { 289 | flush(queueName, flush) { 290 | if (queueName === 'render') { 291 | deferredFlush = flush; 292 | } else { 293 | flush(); 294 | } 295 | } 296 | } 297 | ); 298 | 299 | bb.schedule('zomg', null, () => { 300 | assert.step('running zomg'); 301 | }); 302 | 303 | bb.schedule('render', null, () => { 304 | assert.step('running render'); 305 | }); 306 | 307 | bb.schedule('afterRender', null, () => { 308 | assert.step('running afterRender'); 309 | }); 310 | 311 | return escapeCurrentMicrotaskQueue() 312 | .then(() => { 313 | bb.run(() => {}); 314 | 315 | assert.verifySteps([ 316 | 'start', 317 | 'running zomg', 318 | 'running render', 319 | 'running afterRender', 320 | ]); 321 | }); 322 | }); 323 | -------------------------------------------------------------------------------- /tests/defer-once-test.ts: -------------------------------------------------------------------------------- 1 | import Backburner from 'backburner'; 2 | 3 | QUnit.module('tests/defer-once'); 4 | 5 | QUnit.test('when passed a function', function(assert) { 6 | assert.expect(1); 7 | 8 | let bb = new Backburner(['one']); 9 | let functionWasCalled = false; 10 | 11 | bb.run(() => { 12 | bb.scheduleOnce('one', () => { 13 | functionWasCalled = true; 14 | }); 15 | }); 16 | 17 | assert.ok(functionWasCalled, 'function was called'); 18 | }); 19 | 20 | QUnit.test('when passed a target and method', function(assert) { 21 | assert.expect(2); 22 | 23 | let bb = new Backburner(['one']); 24 | let functionWasCalled = false; 25 | 26 | bb.run(() => { 27 | bb.scheduleOnce('one', {zomg: 'hi'}, function() { 28 | assert.equal(this.zomg, 'hi', 'the target was properly set'); 29 | functionWasCalled = true; 30 | }); 31 | }); 32 | 33 | assert.ok(functionWasCalled, 'function was called'); 34 | }); 35 | 36 | QUnit.test('when passed a target and method name', function(assert) { 37 | assert.expect(2); 38 | 39 | let bb = new Backburner(['one']); 40 | let functionWasCalled = false; 41 | let targetObject = { 42 | zomg: 'hi', 43 | checkFunction() { 44 | assert.equal(this.zomg, 'hi', 'the target was properly set'); 45 | functionWasCalled = true; 46 | } 47 | }; 48 | 49 | bb.run(() => bb.scheduleOnce('one', targetObject, 'checkFunction')); 50 | 51 | assert.ok(functionWasCalled, 'function was called'); 52 | }); 53 | 54 | QUnit.test('throws when passed a null method', function(assert) { 55 | assert.expect(1); 56 | 57 | function onError(error) { 58 | assert.equal('You attempted to schedule an action in a queue (deferErrors) for a method that doesn\'t exist', error.message); 59 | } 60 | 61 | let bb = new Backburner(['deferErrors'], { 62 | onError 63 | }); 64 | 65 | bb.run(() => bb.scheduleOnce('deferErrors', {zomg: 'hi'}, null)); 66 | }); 67 | 68 | QUnit.test('throws when passed an undefined method', function(assert) { 69 | assert.expect(1); 70 | 71 | function onError(error) { 72 | assert.equal('You attempted to schedule an action in a queue (deferErrors) for a method that doesn\'t exist', error.message); 73 | } 74 | 75 | let bb = new Backburner(['deferErrors'], { 76 | onError 77 | }); 78 | 79 | bb.run(() => bb.deferOnce('deferErrors', {zomg: 'hi'}, undefined)); 80 | }); 81 | 82 | QUnit.test('throws when passed an method name that does not exists on the target', function(assert) { 83 | assert.expect(1); 84 | 85 | function onError(error) { 86 | assert.equal('You attempted to schedule an action in a queue (deferErrors) for a method that doesn\'t exist', error.message); 87 | } 88 | 89 | let bb = new Backburner(['deferErrors'], { 90 | onError 91 | }); 92 | 93 | bb.run(() => bb.deferOnce('deferErrors', {zomg: 'hi'}, 'checkFunction')); 94 | }); 95 | 96 | QUnit.test('when passed a target, method, and arguments', function(assert) { 97 | assert.expect(5); 98 | 99 | let bb = new Backburner(['one']); 100 | let functionWasCalled = false; 101 | 102 | bb.run(() => { 103 | bb.scheduleOnce('one', {zomg: 'hi'}, function(a, b, c) { 104 | assert.equal(this.zomg, 'hi', 'the target was properly set'); 105 | assert.equal(a, 1, 'the first arguments was passed in'); 106 | assert.equal(b, 2, 'the second arguments was passed in'); 107 | assert.equal(c, 3, 'the third arguments was passed in'); 108 | functionWasCalled = true; 109 | }, 1, 2, 3); 110 | }); 111 | 112 | assert.ok(functionWasCalled, 'function was called'); 113 | }); 114 | 115 | QUnit.test('when passed same function twice', function(assert) { 116 | assert.expect(2); 117 | 118 | let bb = new Backburner(['one']); 119 | let i = 0; 120 | let functionWasCalled = false; 121 | 122 | function deferMethod() { 123 | i++; 124 | assert.equal(i, 1, 'Function should be called only once'); 125 | functionWasCalled = true; 126 | } 127 | 128 | bb.run(() => { 129 | bb.scheduleOnce('one', deferMethod); 130 | bb.scheduleOnce('one', deferMethod); 131 | }); 132 | 133 | assert.ok(functionWasCalled, 'function was called only once'); 134 | }); 135 | 136 | QUnit.test('when passed same function twice with same target', function(assert) { 137 | assert.expect(3); 138 | 139 | let bb = new Backburner(['one']); 140 | let i = 0; 141 | let functionWasCalled = false; 142 | 143 | function deferMethod() { 144 | i++; 145 | assert.equal(i, 1, 'Function should be called only once'); 146 | assert.equal(this['first'], 1, 'the target property was set'); 147 | functionWasCalled = true; 148 | } 149 | 150 | let argObj = {first: 1}; 151 | 152 | bb.run(() => { 153 | bb.scheduleOnce('one', argObj, deferMethod); 154 | bb.scheduleOnce('one', argObj, deferMethod); 155 | }); 156 | 157 | assert.ok(functionWasCalled, 'function was called only once'); 158 | }); 159 | 160 | QUnit.test('when passed same function twice with different targets', function(assert) { 161 | assert.expect(3); 162 | 163 | let bb = new Backburner(['one']); 164 | let i = 0; 165 | 166 | function deferMethod() { 167 | i++; 168 | assert.equal(this['first'], 1, 'the target property was set'); 169 | } 170 | 171 | bb.run(() => { 172 | bb.scheduleOnce('one', {first: 1}, deferMethod); 173 | bb.scheduleOnce('one', {first: 1}, deferMethod); 174 | }); 175 | 176 | assert.equal(i, 2, 'function was called twice'); 177 | }); 178 | 179 | QUnit.test('when passed same function twice with same arguments and same target', function(assert) { 180 | assert.expect(4); 181 | 182 | let bb = new Backburner(['one']); 183 | let i = 0; 184 | 185 | function deferMethod(a, b) { 186 | i++; 187 | assert.equal(a, 1, 'First argument is set only one time'); 188 | assert.equal(b, 2, 'Second argument remains same'); 189 | assert.equal(this['first'], 1, 'the target property was set'); 190 | } 191 | 192 | let argObj = {first: 1}; 193 | 194 | bb.run(() => { 195 | bb.scheduleOnce('one', argObj, deferMethod, 1, 2); 196 | bb.scheduleOnce('one', argObj, deferMethod, 1, 2); 197 | }); 198 | 199 | assert.equal(i, 1, 'function was called once'); 200 | }); 201 | 202 | QUnit.test('when passed same function twice with same target and different arguments', function(assert) { 203 | assert.expect(4); 204 | 205 | let bb = new Backburner(['one']); 206 | let i = 0; 207 | 208 | function deferMethod(a, b) { 209 | i++; 210 | assert.equal(a, 3, 'First argument of only second call is set'); 211 | assert.equal(b, 2, 'Second argument remains same'); 212 | assert.equal(this['first'], 1, 'the target property was set'); 213 | } 214 | 215 | let argObj = {first: 1}; 216 | 217 | bb.run(() => { 218 | bb.scheduleOnce('one', argObj, deferMethod, 1, 2); 219 | bb.scheduleOnce('one', argObj, deferMethod, 3, 2); 220 | }); 221 | 222 | assert.equal(i, 1, 'function was called once'); 223 | }); 224 | 225 | QUnit.test('when passed same function twice with different target and different arguments', function(assert) { 226 | assert.expect(7); 227 | 228 | let bb = new Backburner(['one']); 229 | let i = 0; 230 | 231 | function deferMethod(a, b) { 232 | i++; 233 | if (i === 1) { 234 | assert.equal(a, 1, 'First argument set during first call'); 235 | } else { 236 | assert.equal(a, 3, 'First argument set during second call'); 237 | } 238 | assert.equal(b, 2, 'Second argument remains same'); 239 | assert.equal(this['first'], 1, 'the target property was set'); 240 | } 241 | 242 | let argObj = {first: 1}; 243 | 244 | bb.run(() => { 245 | bb.scheduleOnce('one', {first: 1}, deferMethod, 1, 2); 246 | bb.scheduleOnce('one', {first: 1}, deferMethod, 3, 2); 247 | }); 248 | 249 | assert.equal(i, 2, 'function was called twice'); 250 | }); 251 | 252 | QUnit.test('when passed same function with same target after already triggering in current loop', function(assert) { 253 | assert.expect(5); 254 | 255 | let bb = new Backburner(['one', 'two']); 256 | let i = 0; 257 | 258 | function deferMethod(a) { 259 | i++; 260 | assert.equal(a, i, 'Correct argument is set'); 261 | assert.equal(this['first'], 1, 'the target property was set'); 262 | } 263 | 264 | function scheduleMethod() { 265 | bb.scheduleOnce('one', argObj, deferMethod, 2); 266 | } 267 | 268 | let argObj = {first: 1}; 269 | 270 | bb.run(() => { 271 | bb.scheduleOnce('one', argObj, deferMethod, 1); 272 | bb.scheduleOnce('two', argObj, scheduleMethod); 273 | }); 274 | 275 | assert.equal(i, 2, 'function was called twice'); 276 | }); 277 | 278 | QUnit.test('when passed same function with same target after already triggering in current loop', function(assert) { 279 | assert.expect(5); 280 | 281 | let argObj = {first: 1}; 282 | let bb = new Backburner(['one', 'two'], { }); 283 | 284 | let i = 0; 285 | 286 | function deferMethod(a) { 287 | i++; 288 | assert.equal(a, i, 'Correct argument is set'); 289 | assert.equal(this['first'], 1, 'the target property was set'); 290 | } 291 | 292 | function scheduleMethod() { 293 | bb.scheduleOnce('one', argObj, deferMethod, 2); 294 | } 295 | 296 | bb.run(() => { 297 | bb.scheduleOnce('one', argObj, deferMethod, 1); 298 | bb.scheduleOnce('two', argObj, scheduleMethod); 299 | }); 300 | 301 | assert.equal(i, 2, 'function was called twice'); 302 | }); 303 | 304 | QUnit.test('onError', function(assert) { 305 | assert.expect(1); 306 | 307 | function onError(error) { 308 | assert.equal('QUnit.test error', error.message); 309 | } 310 | 311 | let bb = new Backburner(['errors'], { onError }); 312 | 313 | bb.run(() => { 314 | bb.scheduleOnce('errors', () => { 315 | throw new Error('QUnit.test error'); 316 | }); 317 | }); 318 | }); 319 | 320 | QUnit.test('when [queueName, callback, string] args passed', function(assert) { 321 | assert.expect(2); 322 | 323 | let bb = new Backburner(['one']); 324 | let functionWasCalled = false; 325 | 326 | bb.run(() => { 327 | bb.scheduleOnce('one', function(name) { 328 | assert.equal(name, 'batman'); 329 | functionWasCalled = true; 330 | }, 'batman', 100); 331 | }); 332 | 333 | assert.ok(functionWasCalled, 'function was called'); 334 | }); 335 | -------------------------------------------------------------------------------- /dist/loader.js: -------------------------------------------------------------------------------- 1 | var loader, define, requireModule, require, requirejs; 2 | 3 | (function (global) { 4 | 'use strict'; 5 | 6 | function dict() { 7 | var obj = Object.create(null); 8 | obj['__'] = undefined; 9 | delete obj['__']; 10 | return obj; 11 | } 12 | 13 | // Save off the original values of these globals, so we can restore them if someone asks us to 14 | var oldGlobals = { 15 | loader: loader, 16 | define: define, 17 | requireModule: requireModule, 18 | require: require, 19 | requirejs: requirejs 20 | }; 21 | 22 | requirejs = require = requireModule = function (id) { 23 | var pending = []; 24 | var mod = findModule(id, '(require)', pending); 25 | 26 | for (var i = pending.length - 1; i >= 0; i--) { 27 | pending[i].exports(); 28 | } 29 | 30 | return mod.module.exports; 31 | }; 32 | 33 | loader = { 34 | noConflict: function (aliases) { 35 | var oldName, newName; 36 | 37 | for (oldName in aliases) { 38 | if (aliases.hasOwnProperty(oldName)) { 39 | if (oldGlobals.hasOwnProperty(oldName)) { 40 | newName = aliases[oldName]; 41 | 42 | global[newName] = global[oldName]; 43 | global[oldName] = oldGlobals[oldName]; 44 | } 45 | } 46 | } 47 | }, 48 | // Option to enable or disable the generation of default exports 49 | makeDefaultExport: true 50 | }; 51 | 52 | var registry = dict(); 53 | var seen = dict(); 54 | 55 | var uuid = 0; 56 | 57 | function unsupportedModule(length) { 58 | throw new Error('an unsupported module was defined, expected `define(id, deps, module)` instead got: `' + length + '` arguments to define`'); 59 | } 60 | 61 | var defaultDeps = ['require', 'exports', 'module']; 62 | 63 | function Module(id, deps, callback, alias) { 64 | this.uuid = uuid++; 65 | this.id = id; 66 | this.deps = !deps.length && callback.length ? defaultDeps : deps; 67 | this.module = { exports: {} }; 68 | this.callback = callback; 69 | this.hasExportsAsDep = false; 70 | this.isAlias = alias; 71 | this.reified = new Array(deps.length); 72 | 73 | /* 74 | Each module normally passes through these states, in order: 75 | new : initial state 76 | pending : this module is scheduled to be executed 77 | reifying : this module's dependencies are being executed 78 | reified : this module's dependencies finished executing successfully 79 | errored : this module's dependencies failed to execute 80 | finalized : this module executed successfully 81 | */ 82 | this.state = 'new'; 83 | } 84 | 85 | Module.prototype.makeDefaultExport = function () { 86 | var exports = this.module.exports; 87 | if (exports !== null && (typeof exports === 'object' || typeof exports === 'function') && exports['default'] === undefined && Object.isExtensible(exports)) { 88 | exports['default'] = exports; 89 | } 90 | }; 91 | 92 | Module.prototype.exports = function () { 93 | // if finalized, there is no work to do. If reifying, there is a 94 | // circular dependency so we must return our (partial) exports. 95 | if (this.state === 'finalized' || this.state === 'reifying') { 96 | return this.module.exports; 97 | } 98 | 99 | 100 | if (loader.wrapModules) { 101 | this.callback = loader.wrapModules(this.id, this.callback); 102 | } 103 | 104 | this.reify(); 105 | 106 | var result = this.callback.apply(this, this.reified); 107 | this.reified.length = 0; 108 | this.state = 'finalized'; 109 | 110 | if (!(this.hasExportsAsDep && result === undefined)) { 111 | this.module.exports = result; 112 | } 113 | if (loader.makeDefaultExport) { 114 | this.makeDefaultExport(); 115 | } 116 | return this.module.exports; 117 | }; 118 | 119 | Module.prototype.unsee = function () { 120 | this.state = 'new'; 121 | this.module = { exports: {} }; 122 | }; 123 | 124 | Module.prototype.reify = function () { 125 | if (this.state === 'reified') { 126 | return; 127 | } 128 | this.state = 'reifying'; 129 | try { 130 | this.reified = this._reify(); 131 | this.state = 'reified'; 132 | } finally { 133 | if (this.state === 'reifying') { 134 | this.state = 'errored'; 135 | } 136 | } 137 | }; 138 | 139 | Module.prototype._reify = function () { 140 | var reified = this.reified.slice(); 141 | for (var i = 0; i < reified.length; i++) { 142 | var mod = reified[i]; 143 | reified[i] = mod.exports ? mod.exports : mod.module.exports(); 144 | } 145 | return reified; 146 | }; 147 | 148 | Module.prototype.findDeps = function (pending) { 149 | if (this.state !== 'new') { 150 | return; 151 | } 152 | 153 | this.state = 'pending'; 154 | 155 | var deps = this.deps; 156 | 157 | for (var i = 0; i < deps.length; i++) { 158 | var dep = deps[i]; 159 | var entry = this.reified[i] = { exports: undefined, module: undefined }; 160 | if (dep === 'exports') { 161 | this.hasExportsAsDep = true; 162 | entry.exports = this.module.exports; 163 | } else if (dep === 'require') { 164 | entry.exports = this.makeRequire(); 165 | } else if (dep === 'module') { 166 | entry.exports = this.module; 167 | } else { 168 | entry.module = findModule(resolve(dep, this.id), this.id, pending); 169 | } 170 | } 171 | }; 172 | 173 | Module.prototype.makeRequire = function () { 174 | var id = this.id; 175 | var r = function (dep) { 176 | return require(resolve(dep, id)); 177 | }; 178 | r['default'] = r; 179 | r.moduleId = id; 180 | r.has = function (dep) { 181 | return has(resolve(dep, id)); 182 | }; 183 | return r; 184 | }; 185 | 186 | define = function (id, deps, callback) { 187 | var module = registry[id]; 188 | 189 | // If a module for this id has already been defined and is in any state 190 | // other than `new` (meaning it has been or is currently being required), 191 | // then we return early to avoid redefinition. 192 | if (module && module.state !== 'new') { 193 | return; 194 | } 195 | 196 | if (arguments.length < 2) { 197 | unsupportedModule(arguments.length); 198 | } 199 | 200 | if (!Array.isArray(deps)) { 201 | callback = deps; 202 | deps = []; 203 | } 204 | 205 | if (callback instanceof Alias) { 206 | registry[id] = new Module(callback.id, deps, callback, true); 207 | } else { 208 | registry[id] = new Module(id, deps, callback, false); 209 | } 210 | }; 211 | 212 | define.exports = function (name, defaultExport) { 213 | var module = registry[name]; 214 | 215 | // If a module for this name has already been defined and is in any state 216 | // other than `new` (meaning it has been or is currently being required), 217 | // then we return early to avoid redefinition. 218 | if (module && module.state !== 'new') { 219 | return; 220 | } 221 | 222 | module = new Module(name, [], noop, null); 223 | module.module.exports = defaultExport; 224 | module.state = 'finalized'; 225 | registry[name] = module; 226 | 227 | return module; 228 | }; 229 | 230 | function noop() {} 231 | // we don't support all of AMD 232 | // define.amd = {}; 233 | 234 | function Alias(id) { 235 | this.id = id; 236 | } 237 | 238 | define.alias = function (id, target) { 239 | if (arguments.length === 2) { 240 | return define(target, new Alias(id)); 241 | } 242 | 243 | return new Alias(id); 244 | }; 245 | 246 | function missingModule(id, referrer) { 247 | throw new Error('Could not find module `' + id + '` imported from `' + referrer + '`'); 248 | } 249 | 250 | function findModule(id, referrer, pending) { 251 | var mod = registry[id] || registry[id + '/index']; 252 | 253 | while (mod && mod.isAlias) { 254 | mod = registry[mod.id] || registry[mod.id + '/index']; 255 | } 256 | 257 | if (!mod) { 258 | missingModule(id, referrer); 259 | } 260 | 261 | if (pending && mod.state !== 'pending' && mod.state !== 'finalized') { 262 | mod.findDeps(pending); 263 | pending.push(mod); 264 | } 265 | return mod; 266 | } 267 | 268 | function resolve(child, id) { 269 | if (child.charAt(0) !== '.') { 270 | return child; 271 | } 272 | 273 | 274 | var parts = child.split('/'); 275 | var nameParts = id.split('/'); 276 | var parentBase = nameParts.slice(0, -1); 277 | 278 | for (var i = 0, l = parts.length; i < l; i++) { 279 | var part = parts[i]; 280 | 281 | if (part === '..') { 282 | if (parentBase.length === 0) { 283 | throw new Error('Cannot access parent module of root'); 284 | } 285 | parentBase.pop(); 286 | } else if (part === '.') { 287 | continue; 288 | } else { 289 | parentBase.push(part); 290 | } 291 | } 292 | 293 | return parentBase.join('/'); 294 | } 295 | 296 | function has(id) { 297 | return !!(registry[id] || registry[id + '/index']); 298 | } 299 | 300 | requirejs.entries = requirejs._eak_seen = registry; 301 | requirejs.has = has; 302 | requirejs.unsee = function (id) { 303 | findModule(id, '(unsee)', false).unsee(); 304 | }; 305 | 306 | requirejs.clear = function () { 307 | requirejs.entries = requirejs._eak_seen = registry = dict(); 308 | seen = dict(); 309 | }; 310 | 311 | // This code primes the JS engine for good performance by warming the 312 | // JIT compiler for these functions. 313 | define('foo', function () {}); 314 | define('foo/bar', [], function () {}); 315 | define('foo/asdf', ['module', 'exports', 'require'], function (module, exports, require) { 316 | if (require.has('foo/bar')) { 317 | require('foo/bar'); 318 | } 319 | }); 320 | define('foo/baz', [], define.alias('foo')); 321 | define('foo/quz', define.alias('foo')); 322 | define.alias('foo', 'foo/qux'); 323 | define('foo/bar', ['foo', './quz', './baz', './asdf', './bar', '../foo'], function () {}); 324 | define('foo/main', ['foo/bar'], function () {}); 325 | define.exports('foo/exports', {}); 326 | 327 | require('foo/exports'); 328 | require('foo/main'); 329 | require.unsee('foo/bar'); 330 | 331 | requirejs.clear(); 332 | 333 | if (typeof exports === 'object' && typeof module === 'object' && module.exports) { 334 | module.exports = { require: require, define: define }; 335 | } 336 | })(this); -------------------------------------------------------------------------------- /dist/tests/qunit.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * QUnit 2.15.0 3 | * https://qunitjs.com/ 4 | * 5 | * Copyright OpenJS Foundation and other contributors 6 | * Released under the MIT license 7 | * https://jquery.org/license 8 | */ 9 | 10 | /** Font Family and Sizes */ 11 | 12 | /* Style our buttons in a simple way, uninfluenced by the styles 13 | the tested app might load. Don't affect buttons in #qunit-fixture! 14 | https://github.com/qunitjs/qunit/pull/1395 15 | https://github.com/qunitjs/qunit/issues/1437 */ 16 | #qunit-testrunner-toolbar button, 17 | #qunit-testresult button { 18 | font-size: initial; 19 | border: initial; 20 | background-color: buttonface; 21 | } 22 | 23 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult { 24 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 25 | } 26 | 27 | #qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 28 | #qunit-tests { font-size: smaller; } 29 | 30 | 31 | /** Resets */ 32 | 33 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { 34 | margin: 0; 35 | padding: 0; 36 | } 37 | 38 | 39 | /** Fixed headers with scrollable tests */ 40 | 41 | @supports (display: flex) or (display: -webkit-box) { 42 | @media (min-height: 500px) { 43 | #qunit { 44 | position: fixed; 45 | left: 0px; 46 | right: 0px; 47 | top: 0px; 48 | bottom: 0px; 49 | padding: 8px; 50 | display: -webkit-box; 51 | display: flex; 52 | flex-direction: column; 53 | } 54 | 55 | #qunit-tests { 56 | overflow: scroll; 57 | } 58 | 59 | #qunit-banner { 60 | flex: 5px 0 0; 61 | } 62 | } 63 | } 64 | 65 | 66 | /** Header (excluding toolbar) */ 67 | 68 | #qunit-header { 69 | padding: 0.5em 0 0.5em 1em; 70 | 71 | color: #8699A4; 72 | background-color: #0D3349; 73 | 74 | font-size: 1.5em; 75 | line-height: 1em; 76 | font-weight: 400; 77 | 78 | border-radius: 5px 5px 0 0; 79 | } 80 | 81 | #qunit-header a { 82 | text-decoration: none; 83 | color: #C2CCD1; 84 | } 85 | 86 | #qunit-header a:hover, 87 | #qunit-header a:focus { 88 | color: #FFF; 89 | } 90 | 91 | #qunit-banner { 92 | height: 5px; 93 | } 94 | 95 | #qunit-filteredTest { 96 | padding: 0.5em 1em 0.5em 1em; 97 | color: #366097; 98 | background-color: #F4FF77; 99 | } 100 | 101 | #qunit-userAgent { 102 | padding: 0.5em 1em 0.5em 1em; 103 | color: #FFF; 104 | background-color: #2B81AF; 105 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 106 | } 107 | 108 | 109 | /** Toolbar */ 110 | 111 | #qunit-testrunner-toolbar { 112 | padding: 0.5em 1em 0.5em 1em; 113 | color: #5E740B; 114 | background-color: #EEE; 115 | } 116 | 117 | #qunit-testrunner-toolbar .clearfix { 118 | height: 0; 119 | clear: both; 120 | } 121 | 122 | #qunit-testrunner-toolbar label { 123 | display: inline-block; 124 | } 125 | 126 | #qunit-testrunner-toolbar input[type=checkbox], 127 | #qunit-testrunner-toolbar input[type=radio] { 128 | margin: 3px; 129 | vertical-align: -2px; 130 | } 131 | 132 | #qunit-testrunner-toolbar input[type=text] { 133 | box-sizing: border-box; 134 | height: 1.6em; 135 | } 136 | 137 | #qunit-toolbar-filters { 138 | float: right; 139 | } 140 | 141 | .qunit-url-config, 142 | .qunit-filter, 143 | #qunit-modulefilter { 144 | display: inline-block; 145 | line-height: 2.1em; 146 | } 147 | 148 | .qunit-filter, 149 | #qunit-modulefilter { 150 | position: relative; 151 | margin-left: 1em; 152 | } 153 | 154 | .qunit-url-config label { 155 | margin-right: 0.5em; 156 | } 157 | 158 | #qunit-modulefilter-search { 159 | box-sizing: border-box; 160 | min-width: 400px; 161 | } 162 | 163 | #qunit-modulefilter-search-container:after { 164 | position: absolute; 165 | right: 0.3em; 166 | content: "\25bc"; 167 | color: black; 168 | } 169 | 170 | #qunit-modulefilter-dropdown { 171 | /* align with #qunit-modulefilter-search */ 172 | box-sizing: border-box; 173 | min-width: 400px; 174 | position: absolute; 175 | right: 0; 176 | top: 50%; 177 | margin-top: 0.8em; 178 | 179 | border: 1px solid #D3D3D3; 180 | border-top: none; 181 | border-radius: 0 0 .25em .25em; 182 | color: #000; 183 | background-color: #F5F5F5; 184 | z-index: 99; 185 | } 186 | 187 | #qunit-modulefilter-dropdown a { 188 | color: inherit; 189 | text-decoration: none; 190 | } 191 | 192 | #qunit-modulefilter-dropdown .clickable.checked { 193 | font-weight: bold; 194 | color: #000; 195 | background-color: #D2E0E6; 196 | } 197 | 198 | #qunit-modulefilter-dropdown .clickable:hover { 199 | color: #FFF; 200 | background-color: #0D3349; 201 | } 202 | 203 | #qunit-modulefilter-actions { 204 | display: block; 205 | overflow: auto; 206 | 207 | /* align with #qunit-modulefilter-dropdown-list */ 208 | font: smaller/1.5em sans-serif; 209 | } 210 | 211 | #qunit-modulefilter-dropdown #qunit-modulefilter-actions > * { 212 | box-sizing: border-box; 213 | max-height: 2.8em; 214 | display: block; 215 | padding: 0.4em; 216 | } 217 | 218 | #qunit-modulefilter-dropdown #qunit-modulefilter-actions > button { 219 | float: right; 220 | font: inherit; 221 | } 222 | 223 | #qunit-modulefilter-dropdown #qunit-modulefilter-actions > :last-child { 224 | /* insert padding to align with checkbox margins */ 225 | padding-left: 3px; 226 | } 227 | 228 | #qunit-modulefilter-dropdown-list { 229 | max-height: 200px; 230 | overflow-y: auto; 231 | margin: 0; 232 | border-top: 2px groove threedhighlight; 233 | padding: 0.4em 0 0; 234 | font: smaller/1.5em sans-serif; 235 | } 236 | 237 | #qunit-modulefilter-dropdown-list li { 238 | white-space: nowrap; 239 | overflow: hidden; 240 | text-overflow: ellipsis; 241 | } 242 | 243 | #qunit-modulefilter-dropdown-list .clickable { 244 | display: block; 245 | padding-left: 0.15em; 246 | padding-right: 0.5em; 247 | } 248 | 249 | 250 | /** Tests: Pass/Fail */ 251 | 252 | #qunit-tests { 253 | list-style-position: inside; 254 | } 255 | 256 | #qunit-tests li { 257 | padding: 0.4em 1em 0.4em 1em; 258 | border-bottom: 1px solid #FFF; 259 | list-style-position: inside; 260 | } 261 | 262 | #qunit-tests > li { 263 | display: none; 264 | } 265 | 266 | #qunit-tests li.running, 267 | #qunit-tests li.pass, 268 | #qunit-tests li.fail, 269 | #qunit-tests li.skipped, 270 | #qunit-tests li.aborted { 271 | display: list-item; 272 | } 273 | 274 | #qunit-tests.hidepass { 275 | position: relative; 276 | } 277 | 278 | #qunit-tests.hidepass li.running, 279 | #qunit-tests.hidepass li.pass:not(.todo) { 280 | visibility: hidden; 281 | position: absolute; 282 | width: 0; 283 | height: 0; 284 | padding: 0; 285 | border: 0; 286 | margin: 0; 287 | } 288 | 289 | #qunit-tests li strong { 290 | cursor: pointer; 291 | } 292 | 293 | #qunit-tests li.skipped strong { 294 | cursor: default; 295 | } 296 | 297 | #qunit-tests li a { 298 | padding: 0.5em; 299 | color: #C2CCD1; 300 | text-decoration: none; 301 | } 302 | 303 | #qunit-tests li p a { 304 | padding: 0.25em; 305 | color: #6B6464; 306 | } 307 | #qunit-tests li a:hover, 308 | #qunit-tests li a:focus { 309 | color: #000; 310 | } 311 | 312 | #qunit-tests li .runtime { 313 | float: right; 314 | font-size: smaller; 315 | } 316 | 317 | .qunit-assert-list { 318 | margin-top: 0.5em; 319 | padding: 0.5em; 320 | 321 | background-color: #FFF; 322 | 323 | border-radius: 5px; 324 | } 325 | 326 | .qunit-source { 327 | margin: 0.6em 0 0.3em; 328 | } 329 | 330 | .qunit-collapsed { 331 | display: none; 332 | } 333 | 334 | #qunit-tests table { 335 | border-collapse: collapse; 336 | margin-top: 0.2em; 337 | } 338 | 339 | #qunit-tests th { 340 | text-align: right; 341 | vertical-align: top; 342 | padding: 0 0.5em 0 0; 343 | } 344 | 345 | #qunit-tests td { 346 | vertical-align: top; 347 | } 348 | 349 | #qunit-tests pre { 350 | margin: 0; 351 | white-space: pre-wrap; 352 | word-wrap: break-word; 353 | } 354 | 355 | #qunit-tests del { 356 | color: #374E0C; 357 | background-color: #E0F2BE; 358 | text-decoration: none; 359 | } 360 | 361 | #qunit-tests ins { 362 | color: #500; 363 | background-color: #FFCACA; 364 | text-decoration: none; 365 | } 366 | 367 | /*** Test Counts */ 368 | 369 | #qunit-tests b.counts { color: #000; } 370 | #qunit-tests b.passed { color: #5E740B; } 371 | #qunit-tests b.failed { color: #710909; } 372 | 373 | #qunit-tests li li { 374 | padding: 5px; 375 | background-color: #FFF; 376 | border-bottom: none; 377 | list-style-position: inside; 378 | } 379 | 380 | /*** Passing Styles */ 381 | 382 | #qunit-tests li li.pass { 383 | color: #3C510C; 384 | background-color: #FFF; 385 | border-left: 10px solid #C6E746; 386 | } 387 | 388 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 389 | #qunit-tests .pass .test-name { color: #366097; } 390 | 391 | #qunit-tests .pass .test-actual, 392 | #qunit-tests .pass .test-expected { color: #999; } 393 | 394 | #qunit-banner.qunit-pass { background-color: #C6E746; } 395 | 396 | /*** Failing Styles */ 397 | 398 | #qunit-tests li li.fail { 399 | color: #710909; 400 | background-color: #FFF; 401 | border-left: 10px solid #EE5757; 402 | white-space: pre; 403 | } 404 | 405 | #qunit-tests > li:last-child { 406 | border-radius: 0 0 5px 5px; 407 | } 408 | 409 | #qunit-tests .fail { color: #000; background-color: #EE5757; } 410 | #qunit-tests .fail .test-name, 411 | #qunit-tests .fail .module-name { color: #000; } 412 | 413 | #qunit-tests .fail .test-actual { color: #EE5757; } 414 | #qunit-tests .fail .test-expected { color: #008000; } 415 | 416 | #qunit-banner.qunit-fail { background-color: #EE5757; } 417 | 418 | 419 | /*** Aborted tests */ 420 | #qunit-tests .aborted { color: #000; background-color: orange; } 421 | /*** Skipped tests */ 422 | 423 | #qunit-tests .skipped { 424 | background-color: #EBECE9; 425 | } 426 | 427 | #qunit-tests .qunit-todo-label, 428 | #qunit-tests .qunit-skipped-label { 429 | background-color: #F4FF77; 430 | display: inline-block; 431 | font-style: normal; 432 | color: #366097; 433 | line-height: 1.8em; 434 | padding: 0 0.5em; 435 | margin: -0.4em 0.4em -0.4em 0; 436 | } 437 | 438 | #qunit-tests .qunit-todo-label { 439 | background-color: #EEE; 440 | } 441 | 442 | /** Result */ 443 | 444 | #qunit-testresult { 445 | color: #2B81AF; 446 | background-color: #D2E0E6; 447 | 448 | border-bottom: 1px solid #FFF; 449 | } 450 | #qunit-testresult .clearfix { 451 | height: 0; 452 | clear: both; 453 | } 454 | #qunit-testresult .module-name { 455 | font-weight: 700; 456 | } 457 | #qunit-testresult-display { 458 | padding: 0.5em 1em 0.5em 1em; 459 | width: 85%; 460 | float:left; 461 | } 462 | #qunit-testresult-controls { 463 | padding: 0.5em 1em 0.5em 1em; 464 | width: 10%; 465 | float:left; 466 | } 467 | 468 | /** Fixture */ 469 | 470 | #qunit-fixture { 471 | position: absolute; 472 | top: -10000px; 473 | left: -10000px; 474 | width: 1000px; 475 | height: 1000px; 476 | } 477 | -------------------------------------------------------------------------------- /dist/backburner.d.ts: -------------------------------------------------------------------------------- 1 | export { buildPlatform, IPlatform } from './backburner/platform'; 2 | import { buildNext, buildPlatform, IPlatform } from './backburner/platform'; 3 | import DeferredActionQueues from './backburner/deferred-action-queues'; 4 | export type { DeferredActionQueues }; 5 | import { Iterable } from './backburner/iterator-drain'; 6 | import Queue from './backburner/queue'; 7 | export declare type Timer = string | number; 8 | export interface IBackburnerOptions { 9 | defaultQueue?: string; 10 | onBegin?: (currentInstance: DeferredActionQueues, previousInstance: DeferredActionQueues) => void; 11 | onEnd?: (currentInstance: DeferredActionQueues, nextInstance: DeferredActionQueues) => void; 12 | onError?: (error: any, errorRecordedForStack?: any) => void; 13 | onErrorTarget?: any; 14 | onErrorMethod?: string; 15 | mustYield?: () => boolean; 16 | _buildPlatform?: (flush: () => void) => IPlatform; 17 | flush?(queueName: string, flush: () => void): void; 18 | } 19 | export default class Backburner { 20 | static Queue: typeof Queue; 21 | static buildPlatform: typeof buildPlatform; 22 | static buildNext: typeof buildNext; 23 | DEBUG: boolean; 24 | ASYNC_STACKS: boolean; 25 | currentInstance: DeferredActionQueues | null; 26 | options: IBackburnerOptions; 27 | get counters(): { 28 | begin: number; 29 | end: number; 30 | events: { 31 | begin: number; 32 | end: number; 33 | }; 34 | autoruns: { 35 | created: number; 36 | completed: number; 37 | }; 38 | run: number; 39 | join: number; 40 | defer: number; 41 | schedule: number; 42 | scheduleIterable: number; 43 | deferOnce: number; 44 | scheduleOnce: number; 45 | setTimeout: number; 46 | later: number; 47 | throttle: number; 48 | debounce: number; 49 | cancelTimers: number; 50 | cancel: number; 51 | loops: { 52 | total: number; 53 | nested: number; 54 | }; 55 | }; 56 | private _onBegin; 57 | private _onEnd; 58 | private queueNames; 59 | private instanceStack; 60 | private _eventCallbacks; 61 | private _timerTimeoutId; 62 | private _timers; 63 | private _platform; 64 | private _boundRunExpiredTimers; 65 | private _autorun; 66 | private _autorunStack; 67 | private _boundAutorunEnd; 68 | private _defaultQueue; 69 | constructor(queueNames: string[], options?: IBackburnerOptions); 70 | get defaultQueue(): string; 71 | begin(): DeferredActionQueues; 72 | end(): void; 73 | on(eventName: any, callback: any): void; 74 | off(eventName: any, callback: any): void; 75 | run(target: Function): any; 76 | run(target: Function | any | null, method?: Function | string, ...args: any[]): any; 77 | run(target: any | null | undefined, method?: Function, ...args: any[]): any; 78 | join(target: Function): any; 79 | join(target: Function | any | null, method?: Function | string, ...args: any[]): any; 80 | join(target: any | null | undefined, method?: Function, ...args: any[]): any; 81 | /** 82 | * @deprecated please use schedule instead. 83 | */ 84 | defer(queueName: any, target: any, method: any, ...args: any[]): any; 85 | /** 86 | * Schedule the passed function to run inside the specified queue. 87 | */ 88 | schedule(queueName: string, method: Function): any; 89 | schedule(queueName: string, target: T, method: U, ...args: any[]): any; 90 | schedule(queueName: string, target: any, method: any | Function, ...args: any[]): any; 91 | scheduleIterable(queueName: string, iterable: () => Iterable): { 92 | queue: Queue; 93 | target: any; 94 | method: any; 95 | }; 96 | /** 97 | * @deprecated please use scheduleOnce instead. 98 | */ 99 | deferOnce(queueName: any, target: any, method: any, ...args: any[]): any; 100 | /** 101 | * Schedule the passed function to run once inside the specified queue. 102 | */ 103 | scheduleOnce(queueName: string, method: Function): any; 104 | scheduleOnce(queueName: string, target: T, method: U, ...args: any[]): any; 105 | scheduleOnce(queueName: string, target: any | null, method: any | Function, ...args: any[]): any; 106 | /** 107 | * @deprecated use later instead. 108 | */ 109 | setTimeout(...args: any[]): any; 110 | later(...args: any[]): Timer; 111 | later(target: T, methodName: keyof T, wait?: number | string): Timer; 112 | later(target: T, methodName: keyof T, arg1: any, wait?: number | string): Timer; 113 | later(target: T, methodName: keyof T, arg1: any, arg2: any, wait?: number | string): Timer; 114 | later(target: T, methodName: keyof T, arg1: any, arg2: any, arg3: any, wait?: number | string): Timer; 115 | later(thisArg: any | null, method: () => void, wait?: number | string): Timer; 116 | later(thisArg: any | null, method: (arg1: A) => void, arg1: A, wait?: number | string): Timer; 117 | later(thisArg: any | null, method: (arg1: A, arg2: B) => void, arg1: A, arg2: B, wait?: number | string): Timer; 118 | later(thisArg: any | null, method: (arg1: A, arg2: B, arg3: C) => void, arg1: A, arg2: B, arg3: C, wait?: number | string): Timer; 119 | later(method: () => void, wait?: number | string): Timer; 120 | later(method: (arg1: A) => void, arg1: A, wait?: number | string): Timer; 121 | later(method: (arg1: A, arg2: B) => void, arg1: A, arg2: B, wait?: number | string): Timer; 122 | later(method: (arg1: A, arg2: B, arg3: C) => void, arg1: A, arg2: B, arg3: C, wait?: number | string): Timer; 123 | throttle(target: T, methodName: keyof T, wait?: number | string, immediate?: boolean): Timer; 124 | throttle(target: T, methodName: keyof T, arg1: any, wait?: number | string, immediate?: boolean): Timer; 125 | throttle(target: T, methodName: keyof T, arg1: any, arg2: any, wait?: number | string, immediate?: boolean): Timer; 126 | throttle(target: T, methodName: keyof T, arg1: any, arg2: any, arg3: any, wait?: number | string, immediate?: boolean): Timer; 127 | throttle(thisArg: any | null, method: () => void, wait?: number | string, immediate?: boolean): Timer; 128 | throttle(thisArg: any | null, method: (arg1: A) => void, arg1: A, wait?: number | string, immediate?: boolean): Timer; 129 | throttle(thisArg: any | null, method: (arg1: A, arg2: B) => void, arg1: A, arg2: B, wait?: number | string, immediate?: boolean): Timer; 130 | throttle(thisArg: any | null, method: (arg1: A, arg2: B, arg3: C) => void, arg1: A, arg2: B, arg3: C, wait?: number | string, immediate?: boolean): Timer; 131 | throttle(method: () => void, wait?: number | string, immediate?: boolean): Timer; 132 | throttle(method: (arg1: A) => void, arg1: A, wait?: number | string, immediate?: boolean): Timer; 133 | throttle(method: (arg1: A, arg2: B) => void, arg1: A, arg2: B, wait?: number | string, immediate?: boolean): Timer; 134 | throttle(method: (arg1: A, arg2: B, arg3: C) => void, arg1: A, arg2: B, arg3: C, wait?: number | string, immediate?: boolean): Timer; 135 | debounce(target: T, methodName: keyof T, wait: number | string, immediate?: boolean): Timer; 136 | debounce(target: T, methodName: keyof T, arg1: any, wait: number | string, immediate?: boolean): Timer; 137 | debounce(target: T, methodName: keyof T, arg1: any, arg2: any, wait: number | string, immediate?: boolean): Timer; 138 | debounce(target: T, methodName: keyof T, arg1: any, arg2: any, arg3: any, wait: number | string, immediate?: boolean): Timer; 139 | debounce(thisArg: any | null, method: () => void, wait: number | string, immediate?: boolean): Timer; 140 | debounce(thisArg: any | null, method: (arg1: A) => void, arg1: A, wait: number | string, immediate?: boolean): Timer; 141 | debounce(thisArg: any | null, method: (arg1: A, arg2: B) => void, arg1: A, arg2: B, wait: number | string, immediate?: boolean): Timer; 142 | debounce(thisArg: any | null, method: (arg1: A, arg2: B, arg3: C) => void, arg1: A, arg2: B, arg3: C, wait: number | string, immediate?: boolean): Timer; 143 | debounce(method: () => void, wait: number | string, immediate?: boolean): Timer; 144 | debounce(method: (arg1: A) => void, arg1: A, wait: number | string, immediate?: boolean): Timer; 145 | debounce(method: (arg1: A, arg2: B) => void, arg1: A, arg2: B, wait: number | string, immediate?: boolean): Timer; 146 | debounce(method: (arg1: A, arg2: B, arg3: C) => void, arg1: A, arg2: B, arg3: C, wait: number | string, immediate?: boolean): Timer; 147 | cancelTimers(): void; 148 | hasTimers(): boolean; 149 | cancel(timer?: any): any; 150 | ensureInstance(): void; 151 | /** 152 | * Returns debug information related to the current instance of Backburner 153 | * 154 | * @method getDebugInfo 155 | * @returns {Object | undefined} Will return and Object containing debug information if 156 | * the DEBUG flag is set to true on the current instance of Backburner, else undefined. 157 | */ 158 | getDebugInfo(): { 159 | autorun: Error | null | undefined; 160 | counters: { 161 | begin: number; 162 | end: number; 163 | events: { 164 | begin: number; 165 | end: number; 166 | }; 167 | autoruns: { 168 | created: number; 169 | completed: number; 170 | }; 171 | run: number; 172 | join: number; 173 | defer: number; 174 | schedule: number; 175 | scheduleIterable: number; 176 | deferOnce: number; 177 | scheduleOnce: number; 178 | setTimeout: number; 179 | later: number; 180 | throttle: number; 181 | debounce: number; 182 | cancelTimers: number; 183 | cancel: number; 184 | loops: { 185 | total: number; 186 | nested: number; 187 | }; 188 | }; 189 | timers: import("./backburner/interfaces").IQueueItem[]; 190 | instanceStack: (import("./backburner/deferred-action-queues").IDebugInfo | null | undefined)[]; 191 | } | undefined; 192 | private _end; 193 | private _join; 194 | private _run; 195 | private _cancelAutorun; 196 | private _later; 197 | private _cancelLaterTimer; 198 | /** 199 | Trigger an event. Supports up to two arguments. Designed around 200 | triggering transition events from one run loop instance to the 201 | next, which requires an argument for the instance and then 202 | an argument for the next instance. 203 | 204 | @private 205 | @method _trigger 206 | @param {String} eventName 207 | @param {any} arg1 208 | @param {any} arg2 209 | */ 210 | private _trigger; 211 | private _runExpiredTimers; 212 | private _scheduleExpiredTimers; 213 | private _reinstallTimerTimeout; 214 | private _clearTimerTimeout; 215 | private _installTimerTimeout; 216 | private _ensureInstance; 217 | private _scheduleAutorun; 218 | private createTask; 219 | } 220 | -------------------------------------------------------------------------------- /tests/queue-push-unique-test.ts: -------------------------------------------------------------------------------- 1 | import Backburner from 'backburner'; 2 | 3 | let Queue = (Backburner as any).Queue; 4 | 5 | QUnit.module('tests/queue-push-unique'); 6 | let slice = [].slice; 7 | 8 | QUnit.test('pushUnique: 2 different targets', function(assert) { 9 | let queue = new Queue('foo'); 10 | let target1fooWasCalled: string[][] = []; 11 | let target2fooWasCalled: string[][] = []; 12 | let target1 = { 13 | foo() { 14 | target1fooWasCalled.push(slice.call(arguments)); 15 | } 16 | }; 17 | 18 | let target2 = { 19 | foo() { 20 | target2fooWasCalled.push(slice.call(arguments)); 21 | } 22 | }; 23 | 24 | queue.pushUnique(target1, target1.foo, ['a']); 25 | queue.pushUnique(target2, target2.foo, ['b']); 26 | 27 | assert.deepEqual(target1fooWasCalled, []); 28 | assert.deepEqual(target2fooWasCalled, []); 29 | 30 | queue.flush(); 31 | 32 | assert.deepEqual(target1fooWasCalled.length, 1, 'expected: target 1.foo to be called only once'); 33 | assert.deepEqual(target1fooWasCalled[0], ['a']); 34 | assert.deepEqual(target2fooWasCalled.length, 1, 'expected: target 2.foo to be called only once'); 35 | assert.deepEqual(target2fooWasCalled[0], ['b']); 36 | }); 37 | 38 | QUnit.test('pushUnique: 1 target, 2 different methods', function(assert) { 39 | let queue = new Queue('foo'); 40 | let target1fooWasCalled: string[][] = []; 41 | let target1barWasCalled: string[][] = []; 42 | let target1 = { 43 | foo: function() { 44 | target1fooWasCalled.push(slice.call(arguments)); 45 | }, 46 | bar: function() { 47 | target1barWasCalled.push(slice.call(arguments)); 48 | } 49 | }; 50 | 51 | queue.pushUnique(target1, target1.foo, ['a']); 52 | queue.pushUnique(target1, target1.bar, ['b']); 53 | 54 | assert.deepEqual(target1fooWasCalled, []); 55 | assert.deepEqual(target1barWasCalled, []); 56 | 57 | queue.flush(); 58 | 59 | assert.deepEqual(target1fooWasCalled.length, 1, 'expected: target 1.foo to be called only once'); 60 | assert.deepEqual(target1fooWasCalled[0], ['a']); 61 | assert.deepEqual(target1barWasCalled.length, 1, 'expected: target 1.bar to be called only once'); 62 | assert.deepEqual(target1barWasCalled[0], ['b']); 63 | }); 64 | 65 | QUnit.test('pushUnique: 1 target, 1 different methods called twice', function(assert) { 66 | let queue = new Queue('foo'); 67 | let target1fooWasCalled: string[][] = []; 68 | let target1 = { 69 | foo: function() { 70 | target1fooWasCalled.push(slice.call(arguments)); 71 | } 72 | }; 73 | 74 | queue.pushUnique(target1, target1.foo, ['a']); 75 | queue.pushUnique(target1, target1.foo, ['b']); 76 | 77 | assert.deepEqual(target1fooWasCalled, []); 78 | 79 | queue.flush(); 80 | 81 | assert.deepEqual(target1fooWasCalled.length, 1, 'expected: target 1.foo to be called only once'); 82 | assert.deepEqual(target1fooWasCalled[0], ['b']); 83 | }); 84 | 85 | QUnit.test('pushUnique: 2 different targets', function(assert) { 86 | let queue = new Queue('foo', {}); 87 | let target1fooWasCalled: string[][] = []; 88 | let target2fooWasCalled: string[][] = []; 89 | let target1 = { 90 | foo: function() { 91 | target1fooWasCalled.push(slice.call(arguments)); 92 | } 93 | }; 94 | 95 | let target2 = { 96 | foo: function() { 97 | target2fooWasCalled.push(slice.call(arguments)); 98 | } 99 | }; 100 | 101 | queue.pushUnique(target1, target1.foo, ['a']); 102 | queue.pushUnique(target2, target2.foo, ['b']); 103 | 104 | assert.deepEqual(target1fooWasCalled, []); 105 | assert.deepEqual(target2fooWasCalled, []); 106 | 107 | queue.flush(); 108 | 109 | assert.deepEqual(target1fooWasCalled.length, 1, 'expected: target 1.foo to be called only once'); 110 | assert.deepEqual(target1fooWasCalled[0], ['a']); 111 | assert.deepEqual(target2fooWasCalled.length, 1, 'expected: target 2.foo to be called only once'); 112 | assert.deepEqual(target2fooWasCalled[0], ['b']); 113 | }); 114 | 115 | QUnit.test('pushUnique: 1 target, 2 different methods', function(assert) { 116 | let queue = new Queue('foo', {}); 117 | let target1fooWasCalled: string[][] = []; 118 | let target1barWasCalled: string[][] = []; 119 | let target1 = { 120 | foo: function() { 121 | target1fooWasCalled.push(slice.call(arguments)); 122 | }, 123 | bar: function() { 124 | target1barWasCalled.push(slice.call(arguments)); 125 | } 126 | }; 127 | 128 | queue.pushUnique(target1, target1.foo, ['a']); 129 | queue.pushUnique(target1, target1.bar, ['b']); 130 | 131 | assert.deepEqual(target1fooWasCalled, []); 132 | assert.deepEqual(target1barWasCalled, []); 133 | 134 | queue.flush(); 135 | 136 | assert.deepEqual(target1fooWasCalled.length, 1, 'expected: target 1.foo to be called only once'); 137 | assert.deepEqual(target1fooWasCalled[0], ['a']); 138 | assert.deepEqual(target1barWasCalled.length, 1, 'expected: target 1.bar to be called only once'); 139 | assert.deepEqual(target1barWasCalled[0], ['b']); 140 | }); 141 | 142 | QUnit.test('pushUnique: 1 target, 1 diffe`rent methods called twice', function(assert) { 143 | let queue = new Queue('foo', {}); 144 | let target1fooWasCalled: string[][] = []; 145 | let target1 = { 146 | foo: function() { 147 | target1fooWasCalled.push(slice.call(arguments)); 148 | } 149 | }; 150 | 151 | queue.pushUnique(target1, target1.foo, ['a']); 152 | queue.pushUnique(target1, target1.foo, ['b']); 153 | 154 | assert.deepEqual(target1fooWasCalled, []); 155 | 156 | queue.flush(); 157 | 158 | assert.deepEqual(target1fooWasCalled.length, 1, 'expected: target 1.foo to be called only once'); 159 | assert.deepEqual(target1fooWasCalled[0], ['b']); 160 | }); 161 | 162 | QUnit.test('pushUnique: 1 target, 2 different methods, second one called twice', function(assert) { 163 | let queue = new Queue('foo', {}); 164 | let target1barWasCalled: string[][] = []; 165 | let target1 = { 166 | foo: function() { 167 | }, 168 | bar: function() { 169 | target1barWasCalled.push(slice.call(arguments)); 170 | } 171 | }; 172 | 173 | queue.pushUnique(target1, target1.foo); 174 | queue.pushUnique(target1, target1.bar, ['a']); 175 | queue.pushUnique(target1, target1.bar, ['b']); 176 | 177 | assert.deepEqual(target1barWasCalled, []); 178 | 179 | queue.flush(); 180 | 181 | assert.deepEqual(target1barWasCalled.length, 1, 'expected: target 1.bar to be called only once'); 182 | }); 183 | 184 | QUnit.test('pushUnique: 2 different targets', function(assert) { 185 | let queue = new Queue('foo', {}); 186 | 187 | let target1fooWasCalled: string[][] = []; 188 | let target2fooWasCalled: string[][] = []; 189 | let target1 = { 190 | foo: function() { 191 | target1fooWasCalled.push(slice.call(arguments)); 192 | } 193 | }; 194 | 195 | let target2 = { 196 | foo: function() { 197 | target2fooWasCalled.push(slice.call(arguments)); 198 | } 199 | }; 200 | queue.pushUnique(target1, target1.foo, ['a']); 201 | queue.pushUnique(target2, target2.foo, ['b']); 202 | 203 | assert.deepEqual(target1fooWasCalled, []); 204 | assert.deepEqual(target2fooWasCalled, []); 205 | 206 | queue.flush(); 207 | 208 | assert.deepEqual(target1fooWasCalled.length, 1, 'expected: target 1.foo to be called only once'); 209 | assert.deepEqual(target1fooWasCalled[0], ['a']); 210 | assert.deepEqual(target2fooWasCalled.length, 1, 'expected: target 2.foo to be called only once'); 211 | assert.deepEqual(target2fooWasCalled[0], ['b']); 212 | }); 213 | 214 | QUnit.test('pushUnique: 1 target, 2 different methods', function(assert) { 215 | let queue = new Queue('foo', {}); 216 | let target1fooWasCalled: string[][] = []; 217 | let target1barWasCalled: string[][] = []; 218 | let target1 = { 219 | foo: function() { 220 | target1fooWasCalled.push(slice.call(arguments)); 221 | }, 222 | bar: function() { 223 | target1barWasCalled.push(slice.call(arguments)); 224 | } 225 | }; 226 | 227 | queue.pushUnique(target1, target1.foo, ['a']); 228 | queue.pushUnique(target1, target1.bar, ['b']); 229 | 230 | assert.deepEqual(target1fooWasCalled, []); 231 | assert.deepEqual(target1barWasCalled, []); 232 | 233 | queue.flush(); 234 | 235 | assert.deepEqual(target1fooWasCalled.length, 1, 'expected: target 1.foo to be called only once'); 236 | assert.deepEqual(target1fooWasCalled[0], ['a']); 237 | assert.deepEqual(target1barWasCalled.length, 1, 'expected: target 1.bar to be called only once'); 238 | assert.deepEqual(target1barWasCalled[0], ['b']); 239 | }); 240 | 241 | QUnit.test('pushUnique: 1 target, 1 different methods called twice', function(assert) { 242 | let queue = new Queue('foo', {}); 243 | let target1fooWasCalled: string[][] = []; 244 | let target1 = { 245 | foo: function() { 246 | target1fooWasCalled.push(slice.call(arguments)); 247 | } 248 | }; 249 | 250 | queue.pushUnique(target1, target1.foo, ['a']); 251 | queue.pushUnique(target1, target1.foo, ['b']); 252 | 253 | assert.deepEqual(target1fooWasCalled, []); 254 | 255 | queue.flush(); 256 | 257 | assert.deepEqual(target1fooWasCalled.length, 1, 'expected: target 1.foo to be called only once'); 258 | assert.deepEqual(target1fooWasCalled[0], ['b']); 259 | }); 260 | 261 | QUnit.test('pushUnique: 1 target, 2 different methods, second one called twice', function(assert) { 262 | let queue = new Queue('foo', {}); 263 | 264 | let target1barWasCalled: string[][] = []; 265 | let target1 = { 266 | foo: function() { 267 | }, 268 | bar: function() { 269 | target1barWasCalled.push(slice.call(arguments)); 270 | } 271 | }; 272 | 273 | queue.pushUnique(target1, target1.foo); 274 | queue.pushUnique(target1, target1.bar, ['a']); 275 | queue.pushUnique(target1, target1.bar, ['b']); 276 | 277 | assert.deepEqual(target1barWasCalled, []); 278 | 279 | queue.flush(); 280 | 281 | assert.equal(target1barWasCalled.length, 1, 'expected: target 1.bar to be called only once'); 282 | }); 283 | 284 | QUnit.test('can cancel property', function(assert) { 285 | let queue = new Queue('foo', {}); 286 | 287 | let target1fooWasCalled: number = 0; 288 | let target2fooWasCalled: number = 0; 289 | let target1 = { 290 | foo: function() { 291 | target1fooWasCalled++; 292 | } 293 | }; 294 | 295 | let target2 = { 296 | foo: function() { 297 | target2fooWasCalled++; 298 | } 299 | }; 300 | 301 | let timer1 = queue.pushUnique(target1, target1.foo); 302 | let timer2 = queue.pushUnique(target2, target2.foo); 303 | 304 | queue.cancel(timer2); 305 | queue.cancel(timer1); 306 | 307 | queue.pushUnique(target1, target1.foo); 308 | queue.pushUnique(target1, target1.foo); 309 | queue.pushUnique(target2, target2.foo); 310 | queue.pushUnique(target2, target2.foo); 311 | 312 | queue.flush(); 313 | 314 | assert.equal(target1fooWasCalled, 1); 315 | assert.equal(target2fooWasCalled, 1); 316 | }); 317 | 318 | QUnit.test('pushUnique: 1 target, 1 method called twice, canceled 2 call', function(assert) { 319 | let queue = new Queue('foo'); 320 | let invocationArgs: string[][] = []; 321 | let target1 = { 322 | foo: function() { 323 | invocationArgs.push(...arguments); 324 | } 325 | }; 326 | 327 | queue.pushUnique(target1, target1.foo, ['a']); 328 | let timer = queue.pushUnique(target1, target1.foo, ['b']); 329 | 330 | assert.deepEqual(invocationArgs, [], 'precond - empty initially'); 331 | 332 | queue.cancel(timer); 333 | 334 | queue.flush(); 335 | 336 | assert.deepEqual(invocationArgs, [], 'still has not been invoked'); 337 | }); 338 | -------------------------------------------------------------------------------- /tests/later-test.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-shadowed-variable*/ 2 | import Backburner from 'backburner'; 3 | import lolex from 'lolex'; 4 | 5 | const originalDateNow = Date.now; 6 | const originalDateValueOf = Date.prototype.valueOf; 7 | 8 | let fakeClock; 9 | QUnit.module('tests/set-timeout-test', { 10 | afterEach() { 11 | Date.now = originalDateNow; 12 | Date.prototype.valueOf = originalDateValueOf; 13 | if (fakeClock) { 14 | fakeClock.uninstall(); 15 | } 16 | } 17 | }); 18 | 19 | QUnit.test('later', function(assert) { 20 | assert.expect(6); 21 | 22 | let bb = new Backburner(['one']); 23 | let step = 0; 24 | let instance; 25 | let done = assert.async(); 26 | 27 | // Force +new Date to return the same result while scheduling 28 | // run.later timers. Otherwise: non-determinism! 29 | let now = +new Date(); 30 | Date.prototype.valueOf = function() { return now; }; 31 | 32 | bb.later(null, () => { 33 | instance = bb.currentInstance; 34 | assert.equal(step++, 0); 35 | }, 10); 36 | 37 | bb.later(null, () => { 38 | assert.equal(step++, 1); 39 | assert.equal(instance, bb.currentInstance, 'same instance'); 40 | }, 10); 41 | 42 | Date.prototype.valueOf = originalDateValueOf; 43 | // spin so that when we execute timers (+new Date()) will be greater than the 44 | // time scheduled above; not a problem in real life as we will never 'wait' 45 | // 0ms 46 | while ((+ new Date()) <= now + 10) {} 47 | 48 | bb.later(null, () => { 49 | assert.equal(step++, 2); 50 | 51 | bb.later(null, () => { 52 | assert.equal(step++, 3); 53 | assert.ok(true, 'Another later will execute correctly'); 54 | done(); 55 | }, 1); 56 | }, 20); 57 | }); 58 | 59 | QUnit.test('later should rely on stubbed `Date.now`', function(assert) { 60 | assert.expect(1); 61 | 62 | let bb = new Backburner(['one']); 63 | let done = assert.async(); 64 | let globalNowWasUsed = false; 65 | 66 | Date.now = function() { 67 | globalNowWasUsed = true; 68 | return originalDateNow(); 69 | }; 70 | 71 | bb.later(() => { 72 | assert.ok(globalNowWasUsed); 73 | done(); 74 | }, 1); 75 | }); 76 | 77 | QUnit.test('later shedules timers correctly after time travel', function(assert) { 78 | assert.expect(2); 79 | 80 | let bb = new Backburner(['one']); 81 | let done = assert.async(); 82 | let start = originalDateNow(); 83 | let now = start; 84 | 85 | Date.now = () => now; 86 | 87 | let called1At = 0; 88 | let called2At = 0; 89 | 90 | bb.later(() => called1At = originalDateNow(), 1000); 91 | 92 | now += 1000; 93 | 94 | bb.later(() => called2At = originalDateNow(), 10); 95 | 96 | now += 10; 97 | 98 | setTimeout(() => { 99 | assert.ok(called1At !== 0, 'timeout 1 was called'); 100 | assert.ok(called2At !== 0, 'timeout 2 was called'); 101 | done(); 102 | }, 20); 103 | }); 104 | 105 | let bb; 106 | QUnit.module('later arguments / arity', { 107 | beforeEach() { 108 | bb = new Backburner(['one']); 109 | }, 110 | afterEach() { 111 | bb = undefined; 112 | if (fakeClock) { 113 | fakeClock.uninstall(); 114 | } 115 | } 116 | }); 117 | 118 | QUnit.test('[callback]', function(assert) { 119 | assert.expect(2); 120 | 121 | let done = assert.async(); 122 | 123 | bb.later(function() { 124 | assert.equal(arguments.length, 0); 125 | assert.ok(true, 'was called'); 126 | done(); 127 | }); 128 | }); 129 | 130 | QUnit.test('[callback, undefined]', function(assert) { 131 | assert.expect(2); 132 | let done = assert.async(); 133 | 134 | bb.later(function() { 135 | assert.equal(arguments.length, 1); 136 | assert.ok(true, 'was called'); 137 | done(); 138 | }, undefined); 139 | }); 140 | 141 | QUnit.test('[null, callback, undefined]', function(assert) { 142 | assert.expect(2); 143 | let done = assert.async(); 144 | 145 | bb.later(null, function() { 146 | assert.equal(arguments.length, 0); 147 | assert.ok(true, 'was called'); 148 | done(); 149 | }); 150 | }); 151 | 152 | QUnit.test('[null, callback, undefined]', function(assert) { 153 | assert.expect(2); 154 | let done = assert.async(); 155 | 156 | bb.later(null, function() { 157 | assert.equal(arguments.length, 1); 158 | assert.ok(true, 'was called'); 159 | done(); 160 | }, undefined); 161 | }); 162 | 163 | QUnit.test('[null, callback, null]', function(assert) { 164 | assert.expect(3); 165 | 166 | let done = assert.async(); 167 | 168 | bb.later(null, function() { 169 | assert.equal(arguments.length, 1); 170 | assert.equal(arguments[0], null); 171 | assert.ok(true, 'was called'); 172 | done(); 173 | }, null); 174 | }); 175 | 176 | QUnit.test('[callback, string, string, string]', function(assert) { 177 | assert.expect(5); 178 | 179 | let done = assert.async(); 180 | 181 | bb.later(function() { 182 | assert.equal(arguments.length, 3); 183 | assert.equal(arguments[0], 'a'); 184 | assert.equal(arguments[1], 'b'); 185 | assert.equal(arguments[2], 'c'); 186 | assert.ok(true, 'was called'); 187 | done(); 188 | }, 'a', 'b', 'c'); 189 | }); 190 | 191 | QUnit.test('[null, callback, string, string, string]', function(assert) { 192 | assert.expect(5); 193 | 194 | let done = assert.async(); 195 | 196 | bb.later(null, function() { 197 | assert.equal(arguments.length, 3); 198 | assert.equal(arguments[0], 'a'); 199 | assert.equal(arguments[1], 'b'); 200 | assert.equal(arguments[2], 'c'); 201 | assert.ok(true, 'was called'); 202 | done(); 203 | }, 'a', 'b', 'c'); 204 | }); 205 | 206 | QUnit.test('[null, callback, string, string, string, number]', function(assert) { 207 | assert.expect(5); 208 | let done = assert.async(); 209 | bb.later(null, function() { 210 | assert.equal(arguments.length, 3); 211 | assert.equal(arguments[0], 'a'); 212 | assert.equal(arguments[1], 'b'); 213 | assert.equal(arguments[2], 'c'); 214 | assert.ok(true, 'was called'); 215 | done(); 216 | }, 'a', 'b', 'c', 10); 217 | }); 218 | 219 | QUnit.test('[null, callback, string, string, string, numericString]', function(assert) { 220 | assert.expect(5); 221 | let done = assert.async(); 222 | bb.later(null, function() { 223 | assert.equal(arguments.length, 3); 224 | assert.equal(arguments[0], 'a'); 225 | assert.equal(arguments[1], 'b'); 226 | assert.equal(arguments[2], 'c'); 227 | assert.ok(true, 'was called'); 228 | done(); 229 | }, 'a', 'b', 'c', '1'); 230 | }); 231 | 232 | QUnit.test('[obj, string]', function(assert) { 233 | assert.expect(1); 234 | let done = assert.async(); 235 | bb.later({ 236 | bro() { 237 | assert.ok(true, 'was called'); 238 | done(); 239 | } 240 | }, 'bro'); 241 | }); 242 | 243 | QUnit.test('[obj, string, value]', function(assert) { 244 | assert.expect(3); 245 | let done = assert.async(); 246 | bb.later({ 247 | bro() { 248 | assert.equal(arguments.length, 1); 249 | assert.equal(arguments[0], 'value'); 250 | assert.ok(true, 'was called'); 251 | done(); 252 | } 253 | }, 'bro', 'value'); 254 | }); 255 | 256 | QUnit.test('[obj, string, value, number]', function(assert) { 257 | let done = assert.async(); 258 | bb.later({ 259 | bro() { 260 | assert.equal(arguments.length, 1); 261 | assert.equal(arguments[0], 'value'); 262 | assert.ok(true, 'was called'); 263 | done(); 264 | } 265 | }, 'bro', 'value', 1); 266 | }); 267 | 268 | QUnit.test('[obj, string, value, numericString]', function(assert) { 269 | let done = assert.async(); 270 | bb.later({ 271 | bro() { 272 | assert.equal(arguments.length, 1); 273 | assert.equal(arguments[0], 'value'); 274 | assert.ok(true, 'was called'); 275 | done(); 276 | } 277 | }, 'bro', 'value', '1'); 278 | }); 279 | 280 | QUnit.test('onError', function(assert) { 281 | assert.expect(1); 282 | 283 | let done = assert.async(); 284 | 285 | function onError(error) { 286 | assert.equal('test error', error.message); 287 | done(); 288 | } 289 | 290 | bb = new Backburner(['errors'], { onError }); 291 | 292 | bb.later(() => { throw new Error('test error'); }, 1); 293 | }); 294 | 295 | QUnit.test('later doesn\'t trigger twice with earlier later', function(assert) { 296 | assert.expect(4); 297 | 298 | bb = new Backburner(['one']); 299 | let called1 = 0; 300 | let called2 = 0; 301 | let beginCalls = 0; 302 | let endCalls = 0; 303 | let oldBegin = bb.begin; 304 | let oldEnd = bb.end; 305 | let done = assert.async(); 306 | 307 | bb.begin = function() { 308 | beginCalls++; 309 | oldBegin.call(bb); 310 | }; 311 | 312 | bb.end = function() { 313 | endCalls++; 314 | oldEnd.call(bb); 315 | }; 316 | 317 | bb.later(() => called1++, 50); 318 | bb.later(() => called2++, 10); 319 | 320 | setTimeout(() => { 321 | assert.equal(called1, 1, 'timeout 1 was called once'); 322 | assert.equal(called2, 1, 'timeout 2 was called once'); 323 | assert.equal(beginCalls, 2, 'begin() was called twice'); 324 | assert.equal(endCalls, 2, 'end() was called twice'); 325 | done(); 326 | }, 100); 327 | }); 328 | 329 | QUnit.test('later with two Backburner instances', function(assert) { 330 | assert.expect(8); 331 | 332 | let steps = 0; 333 | let done = assert.async(); 334 | let bb1 = new Backburner(['one'], { 335 | onBegin() { 336 | assert.equal(++steps, 4); 337 | } 338 | }); 339 | let bb2 = new Backburner(['one'], { 340 | onBegin() { 341 | assert.equal(++steps, 6); 342 | } 343 | }); 344 | 345 | assert.equal(++steps, 1); 346 | 347 | bb1.later(() => assert.equal(++steps, 5), 10); 348 | 349 | assert.equal(++steps, 2); 350 | 351 | bb2.later(() => assert.equal(++steps, 7), 10); 352 | 353 | assert.equal(++steps, 3); 354 | 355 | setTimeout(() => { 356 | assert.equal(++steps, 8); 357 | done(); 358 | }, 50); 359 | }); 360 | 361 | QUnit.test('expired timeout doesn\'t hang when setting a new timeout', function(assert) { 362 | assert.expect(3); 363 | 364 | let called1At = 0; 365 | let called2At = 0; 366 | let done = assert.async(); 367 | 368 | bb.later(() => called1At = Date.now(), 1); 369 | 370 | // Block JS to simulate https://github.com/ebryn/backburner.js/issues/135 371 | let waitUntil = Date.now() + 5; 372 | while (Date.now() < waitUntil) { } 373 | 374 | bb.later(() => called2At = Date.now(), 50); 375 | 376 | setTimeout(() => { 377 | assert.ok(called1At !== 0, 'timeout 1 was called'); 378 | assert.ok(called2At !== 0, 'timeout 2 was called'); 379 | assert.ok(called2At - called1At > 10, 'timeout 1 did not wait for timeout 2'); 380 | done(); 381 | }, 60); 382 | }); 383 | 384 | QUnit.test('NaN timeout doesn\'t hang other timeouts', function(assert) { 385 | assert.expect(2); 386 | 387 | let done = assert.async(); 388 | let called1At = 0; 389 | let called2At = 0; 390 | 391 | bb.later(() => called1At = Date.now(), 1); 392 | bb.later(() => {}, NaN); 393 | bb.later(() => called2At = Date.now(), 10); 394 | 395 | setTimeout(() => { 396 | assert.ok(called1At !== 0, 'timeout 1 was called'); 397 | assert.ok(called2At !== 0, 'timeout 2 was called'); 398 | done(); 399 | }, 20); 400 | }); 401 | 402 | QUnit.test('when [callback, string] args passed', function(assert) { 403 | assert.expect(1); 404 | let done = assert.async(); 405 | 406 | let bb = new Backburner(['one']); 407 | 408 | bb.later(function(name) { 409 | assert.equal(name, 'batman'); 410 | done(); 411 | }, 'batman', 0); 412 | }); 413 | 414 | QUnit.test('can be ran "early" with fake timers GH#351', function(assert) { 415 | assert.expect(1); 416 | let done = assert.async(); 417 | 418 | let bb = new Backburner(['one']); 419 | 420 | fakeClock = lolex.install(); 421 | 422 | let startTime = originalDateNow(); 423 | bb.later(function(name) { 424 | let endTime = originalDateNow(); 425 | assert.ok(endTime - startTime < 100, 'did not wait for 5s to run timer'); 426 | done(); 427 | }, 5000); 428 | 429 | fakeClock.tick(5001); 430 | }); 431 | 432 | QUnit.test('debounce called before later', function(assert) { 433 | assert.expect(1); 434 | 435 | let done = assert.async(1); 436 | let bb = new Backburner(['one']); 437 | let func = function() {}; 438 | 439 | bb.run(() => { 440 | bb.debounce(func, 1000); 441 | setTimeout(function() { 442 | bb.debounce(func, 1000); 443 | }, 50); 444 | 445 | let before = Date.now(); 446 | 447 | bb.later(function() { 448 | let diff = Date.now() - before; 449 | assert.ok(diff < 1010, '.later called with too much delay'); 450 | done(); 451 | }, 1000); 452 | }); 453 | 454 | }); 455 | 456 | QUnit.test('boundRunExpiredTimers is called once when first timer canceled', function(assert) { 457 | let done = assert.async(1); 458 | 459 | let bb = new Backburner(['one']); 460 | 461 | let timer = bb.later(function() {}, 500); 462 | bb.cancel(timer); 463 | 464 | let boundRunExpiredTimers = bb['_boundRunExpiredTimers']; 465 | bb['_boundRunExpiredTimers'] = function() { 466 | assert.ok(true); 467 | done(); 468 | return boundRunExpiredTimers.apply(bb, arguments); 469 | }; 470 | 471 | bb.later(function() {}, 800); 472 | }); 473 | -------------------------------------------------------------------------------- /tests/throttle-test.ts: -------------------------------------------------------------------------------- 1 | import Backburner from 'backburner'; 2 | 3 | QUnit.module('tests/throttle'); 4 | 5 | QUnit.test('throttle', function(assert) { 6 | assert.expect(18); 7 | 8 | let bb = new Backburner(['zomg']); 9 | let step = 0; 10 | let done = assert.async(); 11 | 12 | let wasCalled = false; 13 | function throttler() { 14 | assert.ok(!wasCalled); 15 | wasCalled = true; 16 | } 17 | 18 | // let's throttle the function `throttler` for 40ms 19 | // it will be executed in 40ms 20 | bb.throttle(null, throttler, 40, false); 21 | assert.equal(step++, 0); 22 | 23 | // let's schedule `throttler` to run in 10ms 24 | setTimeout(() => { 25 | assert.equal(step++, 1); 26 | assert.ok(!wasCalled); 27 | bb.throttle(null, throttler, false); 28 | }, 10); 29 | 30 | // let's schedule `throttler` to run again in 20ms 31 | setTimeout(() => { 32 | assert.equal(step++, 2); 33 | assert.ok(!wasCalled); 34 | bb.throttle(null, throttler, false); 35 | }, 20); 36 | 37 | // let's schedule `throttler` to run yet again in 30ms 38 | setTimeout(() => { 39 | assert.equal(step++, 3); 40 | assert.ok(!wasCalled); 41 | bb.throttle(null, throttler, false); 42 | }, 30); 43 | 44 | // at 40ms, `throttler` will get called once 45 | 46 | // now, let's schedule an assertion to occur at 50ms, 47 | // 10ms after `throttler` has been called 48 | setTimeout(() => { 49 | assert.equal(step++, 4); 50 | assert.ok(wasCalled); 51 | }, 50); 52 | 53 | // great, we've made it this far, there's one more thing 54 | // we need to test. we want to make sure we can call `throttle` 55 | // again with the same target/method after it has executed 56 | // at the 60ms mark, let's schedule another call to `throttle` 57 | setTimeout(() => { 58 | wasCalled = false; // reset the flag 59 | 60 | // assert call order 61 | assert.equal(step++, 5); 62 | 63 | // call throttle for the second time 64 | bb.throttle(null, throttler, 100, false); 65 | 66 | // assert that it is called in the future and not blackholed 67 | setTimeout(() => { 68 | assert.equal(step++, 6); 69 | assert.ok(wasCalled, 'Another throttle call with the same function can be executed later'); 70 | }, 110); 71 | }, 60); 72 | 73 | setTimeout(() => { 74 | wasCalled = false; // reset the flag 75 | 76 | // assert call order 77 | assert.equal(step++, 7); 78 | 79 | // call throttle again that time using a string number like time interval 80 | bb.throttle(null, throttler, '100', false); 81 | 82 | // assert that it is called in the future and not blackholed 83 | setTimeout(() => { 84 | assert.equal(step++, 8); 85 | assert.ok(wasCalled, 'Throttle accept a string number like time interval'); 86 | done(); 87 | }, 110); 88 | }, 180); 89 | }); 90 | 91 | QUnit.test('throttle with cancelTimers', function(assert) { 92 | assert.expect(1); 93 | 94 | let count = 0; 95 | let bb = new Backburner(['zomg']); 96 | 97 | // Throttle a no-op 10ms 98 | bb.throttle(null, () => { /* no-op */ }, 10, false); 99 | 100 | try { 101 | bb.cancelTimers(); 102 | } catch (e) { 103 | count++; 104 | } 105 | 106 | assert.equal(count, 0, 'calling cancelTimers while something is being throttled does not throw an error'); 107 | }); 108 | 109 | QUnit.test('throttled function is called with final argument', function(assert) { 110 | assert.expect(1); 111 | let done = assert.async(); 112 | 113 | let count = 0; 114 | let bb = new Backburner(['zomg']); 115 | 116 | function throttled(arg) { 117 | assert.equal(arg, 'bus'); 118 | done(); 119 | } 120 | 121 | bb.throttle(null, throttled, 'car' , 10, false); 122 | bb.throttle(null, throttled, 'bicycle' , 10, false); 123 | bb.throttle(null, throttled, 'bus' , 10, false); 124 | }); 125 | 126 | QUnit.test('throttle returns same timer', function(assert) { 127 | assert.expect(2); 128 | 129 | let bb = new Backburner(['joker']); 130 | 131 | function throttled1() {} 132 | function throttled2() {} 133 | 134 | let timer1 = bb.throttle(null, throttled1, 10); 135 | let timer2 = bb.throttle(null, throttled2, 10); 136 | let timer3 = bb.throttle(null, throttled1, 10); 137 | let timer4 = bb.throttle(null, throttled2, 10); 138 | 139 | assert.equal(timer1, timer3); 140 | assert.equal(timer2, timer4); 141 | }); 142 | 143 | QUnit.test('throttle leading edge', function(assert) { 144 | assert.expect(10); 145 | 146 | let bb = new Backburner(['zerg']); 147 | let throttle; 148 | let throttle2; 149 | let wasCalled = false; 150 | let done = assert.async(); 151 | 152 | function throttler() { 153 | assert.ok(!wasCalled, 'throttler hasn\'t been called yet'); 154 | wasCalled = true; 155 | } 156 | 157 | // let's throttle the function `throttler` for 40ms 158 | // it will be executed immediately, but throttled for the future hits 159 | throttle = bb.throttle(null, throttler, 40); 160 | 161 | assert.ok(wasCalled, 'function was executed immediately'); 162 | 163 | wasCalled = false; 164 | // let's schedule `throttler` to run again, it shouldn't be allowed to queue for another 40 msec 165 | throttle2 = bb.throttle(null, throttler, 40); 166 | 167 | assert.equal(throttle, throttle2, 'No new throttle was inserted, returns old throttle'); 168 | 169 | setTimeout(() => { 170 | assert.ok(!wasCalled, 'attempt to call throttle again didn\'t happen'); 171 | 172 | throttle = bb.throttle(null, throttler, 40); 173 | assert.ok(wasCalled, 'newly inserted throttle after timeout functioned'); 174 | 175 | assert.ok(bb.cancel(throttle), 'wait time of throttle was cancelled'); 176 | 177 | wasCalled = false; 178 | throttle2 = bb.throttle(null, throttler, 40); 179 | assert.notEqual(throttle, throttle2, 'the throttle is different'); 180 | assert.ok(wasCalled, 'throttle was inserted and run immediately after cancel'); 181 | done(); 182 | }, 60); 183 | }); 184 | 185 | QUnit.test('throttle returns timer information usable for cancelling', function(assert) { 186 | assert.expect(3); 187 | 188 | let done = assert.async(); 189 | let bb = new Backburner(['batman']); 190 | let wasCalled = false; 191 | 192 | function throttler() { 193 | assert.ok(false, 'this method shouldn\'t be called'); 194 | wasCalled = true; 195 | } 196 | 197 | let timer = bb.throttle(null, throttler, 1, false); 198 | 199 | assert.ok(bb.cancel(timer), 'the timer is cancelled'); 200 | 201 | // should return false second time around 202 | assert.ok(!bb.cancel(timer), 'the timer no longer exists in the list'); 203 | 204 | setTimeout(() => { 205 | assert.ok(!wasCalled, 'the timer wasn\'t called after waiting'); 206 | done(); 207 | }, 60); 208 | }); 209 | 210 | QUnit.test('throttler cancel after it\'s executed returns false', function(assert) { 211 | assert.expect(3); 212 | 213 | let bb = new Backburner(['darkknight']); 214 | let done = assert.async(); 215 | 216 | let wasCalled = false; 217 | 218 | function throttler() { 219 | assert.ok(true, 'the throttled method was called'); 220 | wasCalled = true; 221 | } 222 | 223 | let timer = bb.throttle(null, throttler, 1); 224 | 225 | setTimeout(() => { 226 | assert.ok(!bb.cancel(timer), 'no timer existed to cancel'); 227 | assert.ok(wasCalled, 'the timer was actually called'); 228 | done(); 229 | }, 10); 230 | }); 231 | 232 | QUnit.test('throttler returns the appropriate timer to cancel if the old item still exists', function(assert) { 233 | assert.expect(5); 234 | 235 | let bb = new Backburner(['robin']); 236 | let wasCalled = false; 237 | let done = assert.async(); 238 | 239 | function throttler() { 240 | assert.ok(true, 'the throttled method was called'); 241 | wasCalled = true; 242 | } 243 | 244 | let timer = bb.throttle(null, throttler, 1); 245 | let timer2 = bb.throttle(null, throttler, 1); 246 | 247 | assert.deepEqual(timer, timer2, 'the same timer was returned'); 248 | 249 | setTimeout(() => { 250 | bb.throttle(null, throttler, 1); 251 | assert.ok(!bb.cancel(timer), 'the second timer isn\'t removed, despite appearing to be the same item'); 252 | assert.ok(wasCalled, 'the timer was actually called'); 253 | done(); 254 | }, 10); 255 | 256 | }); 257 | 258 | QUnit.test('throttle without a target, without args', function(assert) { 259 | let done = assert.async(); 260 | let bb = new Backburner(['batman']); 261 | let calledCount = 0; 262 | let calledWith = new Array(); 263 | function throttled(...args) { 264 | calledCount++; 265 | calledWith.push(args); 266 | } 267 | 268 | bb.throttle(throttled, 10); 269 | bb.throttle(throttled, 10); 270 | bb.throttle(throttled, 10); 271 | assert.equal(calledCount, 1, 'throttle method was called immediately'); 272 | assert.deepEqual(calledWith, [ [] ], 'throttle method was called with the correct arguments'); 273 | 274 | setTimeout(() => { 275 | bb.throttle(throttled, 10); 276 | assert.equal(calledCount, 1, 'throttle method was not called again within the time window'); 277 | }, 0); 278 | 279 | setTimeout(() => { 280 | assert.equal(calledCount, 1, 'throttle method was was only called once'); 281 | done(); 282 | }, 20); 283 | }); 284 | 285 | QUnit.test('throttle without a target, without args - can be canceled', function(assert) { 286 | let bb = new Backburner(['batman']); 287 | 288 | let fooCalledCount = 0; 289 | let barCalledCount = 0; 290 | function foo() { 291 | fooCalledCount++; 292 | } 293 | function bar() { 294 | barCalledCount++; 295 | } 296 | 297 | bb.throttle(foo, 10); 298 | bb.throttle(foo, 10); 299 | assert.equal(fooCalledCount, 1, 'foo was called immediately, then throttle'); 300 | 301 | bb.throttle(bar, 10); 302 | let timer = bb.throttle(bar, 10); 303 | assert.equal(barCalledCount, 1, 'bar was called immediately, then throttle'); 304 | 305 | bb.cancel(timer); 306 | bb.throttle(bar, 10); 307 | assert.equal(barCalledCount, 2, 'after canceling the prior throttle, bar was called again'); 308 | }); 309 | 310 | QUnit.test('throttle without a target, without args, not immediate', function(assert) { 311 | let done = assert.async(); 312 | let bb = new Backburner(['batman']); 313 | let calledCount = 0; 314 | let calledWith = new Array(); 315 | function throttled(...args) { 316 | calledCount++; 317 | calledWith.push(args); 318 | } 319 | 320 | bb.throttle(throttled, 10, false); 321 | bb.throttle(throttled, 10, false); 322 | bb.throttle(throttled, 10, false); 323 | assert.equal(calledCount, 0, 'throttle method was not called immediately'); 324 | 325 | setTimeout(() => { 326 | assert.equal(calledCount, 0, 'throttle method was not called in next tick'); 327 | bb.throttle(throttled, 10, false); 328 | }, 0); 329 | 330 | setTimeout(() => { 331 | assert.equal(calledCount, 1, 'throttle method was was only called once'); 332 | assert.deepEqual(calledWith, [ [] ], 'throttle method was called with the correct arguments'); 333 | done(); 334 | }, 20); 335 | }); 336 | 337 | QUnit.test('throttle without a target, without args, not immediate - can be canceled', function(assert) { 338 | let done = assert.async(); 339 | let bb = new Backburner(['batman']); 340 | 341 | let fooCalledCount = 0; 342 | let barCalledCount = 0; 343 | function foo() { 344 | fooCalledCount++; 345 | } 346 | function bar() { 347 | barCalledCount++; 348 | } 349 | 350 | bb.throttle(foo, 10, false); 351 | bb.throttle(foo, 10, false); 352 | assert.equal(fooCalledCount, 0, 'foo was not called immediately'); 353 | 354 | bb.throttle(bar, 10, false); 355 | let timer = bb.throttle(bar, 10, false); 356 | assert.equal(barCalledCount, 0, 'bar was not called immediately'); 357 | 358 | setTimeout(() => { 359 | assert.equal(fooCalledCount, 0, 'foo was not called within the time window'); 360 | assert.equal(barCalledCount, 0, 'bar was not called within the time window'); 361 | 362 | bb.cancel(timer); 363 | }, 0); 364 | 365 | setTimeout(() => { 366 | assert.equal(fooCalledCount, 1, 'foo ran'); 367 | assert.equal(barCalledCount, 0, 'bar was properly canceled'); 368 | 369 | bb.throttle(bar, 10, false); 370 | 371 | setTimeout(() => { 372 | assert.equal(barCalledCount, 1, 'bar was able to run after being canceled'); 373 | done(); 374 | }, 20); 375 | }, 20); 376 | }); 377 | 378 | QUnit.test('throttle without a target, with args', function(assert) { 379 | let bb = new Backburner(['batman']); 380 | let calledWith: object[] = []; 381 | function throttled(first) { 382 | calledWith.push(first); 383 | } 384 | 385 | let foo = { isFoo: true }; 386 | let bar = { isBar: true }; 387 | let baz = { isBaz: true }; 388 | bb.throttle(throttled, foo, 10); 389 | bb.throttle(throttled, bar, 10); 390 | bb.throttle(throttled, baz, 10); 391 | 392 | assert.deepEqual(calledWith, [{ isFoo: true }], 'throttle method was only called once, with correct argument'); 393 | }); 394 | 395 | QUnit.test('throttle without a target, with args - can be canceled', function(assert) { 396 | let done = assert.async(); 397 | let bb = new Backburner(['batman']); 398 | let calledCount = 0; 399 | let calledWith: object[] = []; 400 | function throttled(first) { 401 | calledCount++; 402 | calledWith.push(first); 403 | } 404 | 405 | let foo = { isFoo: true }; 406 | let bar = { isBar: true }; 407 | let baz = { isBaz: true }; 408 | let qux = { isQux: true }; 409 | bb.throttle(throttled, foo, 10); 410 | bb.throttle(throttled, bar, 10); 411 | let timer = bb.throttle(throttled, baz, 10); 412 | 413 | assert.deepEqual(calledWith, [{ isFoo: true }], 'throttle method was only called once, with correct argument'); 414 | 415 | setTimeout(() => { 416 | bb.cancel(timer); 417 | bb.throttle(throttled, qux, 10, true); 418 | assert.deepEqual(calledWith, [{ isFoo: true }, { isQux: true }], 'throttle method was called again after canceling prior timer'); 419 | }, 0); 420 | 421 | setTimeout(() => { 422 | assert.deepEqual(calledWith, [{ isFoo: true }, { isQux: true }], 'throttle method was not called again'); 423 | done(); 424 | }, 20); 425 | }); 426 | 427 | QUnit.test('throttle without a target, with args, not immediate', function(assert) { 428 | let done = assert.async(); 429 | let bb = new Backburner(['batman']); 430 | let calledWith: object[] = []; 431 | function throttler(first) { 432 | calledWith.push(first); 433 | } 434 | 435 | let foo = { isFoo: true }; 436 | let bar = { isBar: true }; 437 | let baz = { isBaz: true }; 438 | bb.throttle(throttler, foo, 10, false); 439 | bb.throttle(throttler, bar, 10, false); 440 | bb.throttle(throttler, baz, 10, false); 441 | assert.deepEqual(calledWith, [], 'throttler was not called immediately'); 442 | 443 | setTimeout(() => { 444 | assert.deepEqual(calledWith, [{ isBaz: true }], 'debounce method was only called once, with correct argument'); 445 | done(); 446 | }, 20); 447 | }); 448 | 449 | QUnit.test('throttle without a target, with args, not immediate - can be canceled', function(assert) { 450 | let done = assert.async(); 451 | let bb = new Backburner(['batman']); 452 | let calledCount = 0; 453 | let calledWith: object[] = []; 454 | function throttled(first) { 455 | calledCount++; 456 | calledWith.push(first); 457 | } 458 | 459 | let foo = { isFoo: true }; 460 | let bar = { isBar: true }; 461 | let baz = { isBaz: true }; 462 | bb.throttle(throttled, foo, 10, false); 463 | bb.throttle(throttled, bar, 10, false); 464 | let timer = bb.throttle(throttled, baz, 10, false); 465 | assert.equal(calledCount, 0, 'throttle method was not called immediately'); 466 | 467 | setTimeout(() => { 468 | assert.deepEqual(calledWith, [], 'throttle method has not been called on next tick'); 469 | bb.cancel(timer); 470 | }, 0); 471 | 472 | setTimeout(() => { 473 | assert.deepEqual(calledWith, [], 'throttle method is not called when canceled'); 474 | done(); 475 | }, 20); 476 | }); 477 | 478 | QUnit.test('onError', function(assert) { 479 | assert.expect(1); 480 | 481 | function onError(error) { 482 | assert.equal('test error', error.message); 483 | } 484 | 485 | let bb = new Backburner(['errors'], { 486 | onError: onError 487 | }); 488 | 489 | bb.throttle(null, () => { 490 | throw new Error('test error'); 491 | }, 20); 492 | }); 493 | 494 | QUnit.test('throttle + immediate joins existing run loop instances', function(assert) { 495 | assert.expect(1); 496 | 497 | function onError(error) { 498 | assert.equal('test error', error.message); 499 | } 500 | 501 | let bb = new Backburner(['errors'], { 502 | onError: onError 503 | }); 504 | 505 | bb.run(() => { 506 | let parentInstance = bb.currentInstance; 507 | bb.throttle(null, () => { 508 | assert.equal(bb.currentInstance, parentInstance); 509 | }, 20, true); 510 | }); 511 | }); 512 | 513 | QUnit.test('when [callback, string] args passed', function(assert) { 514 | assert.expect(2); 515 | 516 | let bb = new Backburner(['one']); 517 | let functionWasCalled = false; 518 | 519 | bb.run(() => { 520 | bb.throttle(function(name) { 521 | assert.equal(name, 'batman'); 522 | functionWasCalled = true; 523 | }, 'batman', 200); 524 | }); 525 | 526 | assert.ok(functionWasCalled, 'function was called'); 527 | }); 528 | --------------------------------------------------------------------------------