├── .gitignore ├── .travis.yml ├── README.md ├── bower.json ├── complexExample.png ├── example.png ├── index.js ├── package.json ├── test ├── complex-example.js ├── example.js └── test.js └── types └── index.d.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Travis build status](https://www.travis-ci.org/piercus/fake-progress.svg?branch=master) 2 | # Fakeprogress 3 | 4 | Simulate smooth progression easily and combine real progression and fake progression. 5 | 6 | ## Install 7 | 8 | ``` 9 | npm install fake-progress 10 | ``` 11 | 12 | ## Basic example 13 | 14 | ```js 15 | var FakeProgress = require("fake-progress"); 16 | 17 | // Create the fake progress with a timeConstant of 10 seconds 18 | // it means that : 19 | // after 10 seconds, progress will be 0.6321 ( = 1-Math.exp(-1) ) 20 | // after 20 seconds, progress will be 0.8646 ( = 1-Math.exp(-2) ) 21 | // and so on 22 | var p = new FakeProgress({ 23 | timeConstant : 10000, 24 | autoStart : true 25 | }); 26 | 27 | var exampleAsyncFunction = function(callback){ 28 | setTimeout(function(){ 29 | callback() 30 | },30000) 31 | }; 32 | 33 | var onEachSecond = function(){ 34 | console.log("Progress is "+(p.progress*100).toFixed(1)+" %"); 35 | }; 36 | 37 | var interval = setInterval(onEachSecond, 1000); 38 | 39 | var onEnd = function(){ 40 | p.end(); 41 | clearInterval(interval); 42 | console.log("Ended. Progress is "+(p.progress*100).toFixed(1)+" %") 43 | }; 44 | 45 | exampleAsyncFunction(onEnd); 46 | ``` 47 | 48 | will print 49 | 50 | ``` 51 | Progress is 8.6 % 52 | Progress is 17.3 % 53 | Progress is 25.2 % 54 | Progress is 32.3 % 55 | ... 56 | ``` 57 | 58 | The chart of progression over time. 59 | 60 | ![Chart of progress](./example.png) 61 | 62 | Until the end is triggered, the progression is following the exponential curve, once "end" is triggered, progression goes to 100%. 63 | 64 | ## More complex 65 | 66 | In this example we will mix 3 functions, A and C are classical async functions, B is an async function with a 'real' progression callback. 67 | 68 | 69 | ### Create 2 async function a, c 70 | 71 | a and c are 2 basic async functions without progress. 72 | ```js 73 | const a = function (cb) { 74 | setTimeout(() => { 75 | cb(); 76 | }, 1000); 77 | }; 78 | 79 | const c = function (cb) { 80 | setTimeout(() => { 81 | cb(); 82 | }, 3000); 83 | }; 84 | ``` 85 | 86 | ### Create a class for event emitter with linear progress 87 | 88 | b will be an instance of an event emmiter that has a progress event 89 | 90 | ```js 91 | const B = function () { 92 | EventEmitter.call(this); 93 | 94 | let count = 0; 95 | const self = this; 96 | const totalCount = 30; 97 | self.emit('start', count / totalCount); 98 | self._intervalId = setInterval(() => { 99 | count++; 100 | if (count >= totalCount) { 101 | self.emit('end', count / totalCount); 102 | clearInterval(self._intervalId); 103 | } else { 104 | self.emit('progress', count / totalCount); 105 | } 106 | }, 100); 107 | }; 108 | 109 | util.inherits(B, EventEmitter); 110 | ``` 111 | 112 | ### Create a fake progress and log his value over time 113 | 114 | ```js 115 | const p = new FakeProgress({}); 116 | 117 | const onEachDeciSecond = function () { 118 | console.log('Progress is ' + (p.progress * 100).toFixed(1) + ' %'); 119 | }; 120 | 121 | onEachDeciSecond(); 122 | 123 | const interval = setInterval(onEachDeciSecond, 100); 124 | ``` 125 | 126 | ### Create sub progress bar of p, for a progress 127 | 128 | A has no progress so we fake his progress. 129 | A succeed in 1000 ms, so we can consider 500 ms is a good timeConstant. 130 | 131 | ```js 132 | const aProgress = p.createSubProgress({ 133 | timeConstant: 500, 134 | end: 0.3, 135 | autoStart: true 136 | }); 137 | ``` 138 | 139 | 140 | ### Call async chain 141 | 142 | Each time on the async chain, subProgress.stop() then call createSubProgress() to create a new subProgress. 143 | 144 | ```js 145 | a(err => { 146 | if (err) { 147 | throw (err); 148 | } 149 | aProgress.stop(); 150 | const bProgress = p.createSubProgress({ 151 | end: 0.8 152 | }); 153 | const b = new B(); 154 | 155 | b.on('progress', progress => { 156 | bProgress.setProgress(progress); 157 | }); 158 | 159 | b.on('end', () => { 160 | bProgress.stop(); 161 | const cProgress = p.createSubProgress({ 162 | timeConstant: 1000, 163 | autoStart: true 164 | }); 165 | c(() => { 166 | cProgress.end(); 167 | onEachDeciSecond(); 168 | clearInterval(interval); 169 | }); 170 | }); 171 | }); 172 | ``` 173 | 174 | ### Call everything 175 | 176 | After each call, stop previous sub, and create a new subProgress for next request. 177 | 178 | ```js 179 | a(function(){ 180 | aProgress.stop(); 181 | var bProgress = p.createSubProgress({ 182 | end : 0.8 183 | }); 184 | var b = new B(); 185 | 186 | b.on('progress', function(progress){ 187 | bProgress.setProgress(progress); 188 | }); 189 | 190 | b.on('end', function(){ 191 | bProgress.stop(); 192 | var cProgress = p.createSubProgress({ 193 | timeConstant : 1000, 194 | autoStart : true 195 | }); 196 | c(function(){ 197 | cProgress.end() 198 | onEachDeciSecond() 199 | clearInterval(interval); 200 | }) 201 | }); 202 | }); 203 | ``` 204 | 205 | ### All together 206 | 207 | see [source](./test/complex-example.js) 208 | 209 | ### Results 210 | 211 | ![Chart of progress](./complexExample.png) 212 | 213 | ## Documentation 214 | 215 | See inside the code, documentated using JSDoc 216 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fake-progress", 3 | "description": "Fake a progress bar using an exponential progress function", 4 | "main": "index.js", 5 | "authors": [ 6 | "Pierre Colle" 7 | ], 8 | "license": "MIT", 9 | "keywords": [ 10 | "progress", 11 | "fake" 12 | ], 13 | "homepage": "https://github.com/piercus/fake-progress", 14 | "ignore": [ 15 | "**/.*", 16 | "node_modules", 17 | "bower_components", 18 | "test", 19 | "tests" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /complexExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piercus/fake-progress/2da32257e17859a003a6496d7d06de9a98a92a5d/complexExample.png -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piercus/fake-progress/2da32257e17859a003a6496d7d06de9a98a92a5d/example.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents a fakeProgress 3 | * @constructor 4 | * @param {object} options - options of the contructor 5 | * @param {object} [options.timeConstant=1000] - the timeConstant in milliseconds (see https://en.wikipedia.org/wiki/Time_constant) 6 | * @param {object} [options.autoStart=false] - if true then the progress auto start 7 | */ 8 | 9 | const FakeProgress = function (opts) { 10 | if (!opts) { 11 | opts = {}; 12 | } 13 | 14 | this.timeConstant = opts.timeConstant || 1000; 15 | this.progress = 0; 16 | this._running = false; 17 | this._intervalFrequency = 100; 18 | this.autoStart = opts.autoStart || false; 19 | this.parent = opts.parent; 20 | this.parentStart = opts.parentStart; 21 | this.parentEnd = opts.parentEnd; 22 | if (this.autoStart) { 23 | this.start(); 24 | } 25 | }; 26 | 27 | /** 28 | * Start fakeProgress instance 29 | * @method 30 | */ 31 | 32 | FakeProgress.prototype.start = function () { 33 | this._time = 0; 34 | this._intervalId = setInterval(this._onInterval.bind(this), this._intervalFrequency); 35 | }; 36 | 37 | FakeProgress.prototype._onInterval = function () { 38 | this._time += this._intervalFrequency; 39 | this.setProgress(1 - Math.exp(-1 * this._time / this.timeConstant)); 40 | }; 41 | 42 | /** 43 | * Stop fakeProgress instance and set progress to 1 44 | * @method 45 | */ 46 | 47 | FakeProgress.prototype.end = function () { 48 | this.stop(); 49 | this.setProgress(1); 50 | }; 51 | 52 | /** 53 | * Stop fakeProgress instance 54 | * @method 55 | */ 56 | 57 | FakeProgress.prototype.stop = function () { 58 | clearInterval(this._intervalId); 59 | this._intervalId = null; 60 | }; 61 | 62 | /** 63 | * Create a sub progress bar under the first progres 64 | * @method 65 | * @param {object} options - options of the FakeProgress contructor 66 | * @param {object} [options.end=1] - the progress in the parent that correspond of 100% of the child 67 | * @param {object} [options.start=fakeprogress.progress] - the progress in the parent that correspond of 0% of the child 68 | */ 69 | 70 | FakeProgress.prototype.createSubProgress = function (opts) { 71 | const parentStart = opts.start || this.progress; 72 | const parentEnd = opts.end || 1; 73 | const options = Object.assign({}, opts, { 74 | parent: this, 75 | parentStart: parentStart, 76 | parentEnd: parentEnd, 77 | start: null, 78 | end: null 79 | }); 80 | 81 | const subProgress = new FakeProgress(options); 82 | return subProgress; 83 | }; 84 | 85 | /** 86 | * SetProgress of the fakeProgress instance and updtae the parent 87 | * @method 88 | * @param {number} progress - the progress 89 | */ 90 | 91 | FakeProgress.prototype.setProgress = function (progress) { 92 | this.progress = progress; 93 | if (this.parent) { 94 | this.parent.setProgress(((this.parentEnd - this.parentStart) * this.progress) + this.parentStart); 95 | } 96 | }; 97 | 98 | if (typeof exports === 'object' && typeof module === 'object') { 99 | module.exports = FakeProgress; 100 | } 101 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fake-progress", 3 | "version": "1.0.4", 4 | "description": "Fake a progress bar using an exponential progress function", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "xo && node test/test.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/piercus/fake-progress.git" 12 | }, 13 | "keywords": [ 14 | "progress", 15 | "fake", 16 | "spinner", 17 | "percentage", 18 | "simulate", 19 | "elegant", 20 | "loading", 21 | "busy" 22 | ], 23 | "author": "Pierre Colle", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/piercus/fake-progress/issues" 27 | }, 28 | "xo": { 29 | "rules": { 30 | "object-shorthand": [ 31 | "error", 32 | "never" 33 | ] 34 | } 35 | }, 36 | "homepage": "https://github.com/piercus/fake-progress#readme", 37 | "devDependencies": { 38 | "assert": "^1.4.1", 39 | "vows": "^0.8.1", 40 | "xo": "^0.18.1" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/complex-example.js: -------------------------------------------------------------------------------- 1 | const util = require('util'); 2 | const EventEmitter = require('events'); 3 | const FakeProgress = require('..'); 4 | 5 | const a = function (cb) { 6 | setTimeout(() => { 7 | cb(); 8 | }, 1000); 9 | }; 10 | 11 | const c = function (cb) { 12 | setTimeout(() => { 13 | cb(); 14 | }, 3000); 15 | }; 16 | 17 | const B = function () { 18 | EventEmitter.call(this); 19 | 20 | let count = 0; 21 | const self = this; 22 | const totalCount = 30; 23 | self.emit('start', count / totalCount); 24 | self._intervalId = setInterval(() => { 25 | count++; 26 | if (count >= totalCount) { 27 | self.emit('end', count / totalCount); 28 | clearInterval(self._intervalId); 29 | } else { 30 | self.emit('progress', count / totalCount); 31 | } 32 | }, 100); 33 | }; 34 | 35 | util.inherits(B, EventEmitter); 36 | 37 | const p = new FakeProgress({}); 38 | 39 | const onEachDeciSecond = function () { 40 | console.log('Progress is ' + (p.progress * 100).toFixed(1) + ' %'); 41 | }; 42 | 43 | onEachDeciSecond(); 44 | 45 | const interval = setInterval(onEachDeciSecond, 100); 46 | 47 | const aProgress = p.createSubProgress({ 48 | timeConstant: 500, 49 | end: 0.3, 50 | autoStart: true 51 | }); 52 | 53 | a(err => { 54 | if (err) { 55 | throw (err); 56 | } 57 | aProgress.stop(); 58 | const bProgress = p.createSubProgress({ 59 | end: 0.8 60 | }); 61 | const b = new B(); 62 | 63 | b.on('progress', progress => { 64 | bProgress.setProgress(progress); 65 | }); 66 | 67 | b.on('end', () => { 68 | bProgress.stop(); 69 | const cProgress = p.createSubProgress({ 70 | timeConstant: 1000, 71 | autoStart: true 72 | }); 73 | c(() => { 74 | cProgress.end(); 75 | onEachDeciSecond(); 76 | clearInterval(interval); 77 | }); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /test/example.js: -------------------------------------------------------------------------------- 1 | const FakeProgress = require('..'); 2 | 3 | // Create the fake progress with a timeConstant of 10 seconds 4 | // it means that : 5 | // after 10 seconds, progress will be 0.6321 ( = 1-Math.exp(-1) ) 6 | // after 20 seconds, progress will be 0.8646 ( = 1-Math.exp(-2) ) 7 | // and so one 8 | const p = new FakeProgress({ 9 | timeConstant: 10000, 10 | autoStart: true 11 | }); 12 | 13 | const exampleAsyncFunction = function (callback) { 14 | setTimeout(() => { 15 | callback(); 16 | }, 30000); 17 | }; 18 | 19 | const onEachSecond = function () { 20 | console.log('Progress is ' + (p.progress * 100).toFixed(1) + ' %'); 21 | }; 22 | 23 | const interval = setInterval(onEachSecond, 1000); 24 | 25 | const onEnd = function () { 26 | p.end(); 27 | clearInterval(interval); 28 | console.log('Ended. Progress is ' + (p.progress * 100).toFixed(1) + ' %'); 29 | }; 30 | 31 | exampleAsyncFunction(onEnd); 32 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | const vows = require('vows'); 2 | const assert = require('assert'); 3 | const util = require('util'); 4 | const EventEmitter = require('events'); 5 | const FakeProgress = require('..'); 6 | 7 | const tolerance = 0.03;// 3% 8 | 9 | vows.describe('fake-progress').addBatch({ 10 | 'fakeProgress default instance': { 11 | topic: function () { 12 | return new FakeProgress(); 13 | }, 14 | 'can be started': function (fakeProgress) { 15 | assert.equal(typeof (fakeProgress.start), 'function'); 16 | }, 17 | 'has a progress over time': function (fakeProgress) { 18 | assert.equal(typeof (fakeProgress.progress), 'number'); 19 | }, 20 | 'can be ended': function (fakeProgress) { 21 | assert.equal(typeof (fakeProgress.end), 'function'); 22 | fakeProgress.end(); 23 | } 24 | }, 25 | 'fakeProgress instance with autoStart': { 26 | topic: function () { 27 | const self = this; 28 | const fakeProgress = new FakeProgress({ 29 | timeConstant: 10000, 30 | autoStart: true 31 | }); 32 | setTimeout(() => { 33 | self.callback(null, fakeProgress); 34 | }, 10000); 35 | }, 36 | 'value is around 1 - Math.exp(-1)': function (fakeProgress) { 37 | const expected = 1 - Math.exp(-1); 38 | 39 | assert(fakeProgress.progress > expected - tolerance, 'fakeProgress.progress must be > ' + (expected - tolerance) + ' and is ' + fakeProgress.progress); 40 | assert(fakeProgress.progress < expected + tolerance, 'fakeProgress.progress must be < ' + (expected + tolerance) + ' and is ' + fakeProgress.progress); 41 | fakeProgress.stop(); 42 | } 43 | }, 44 | 'fakeProgress instance without autoStart': { 45 | topic: function () { 46 | const self = this; 47 | const fakeProgress = new FakeProgress({ 48 | timeConstant: 500 49 | }); 50 | setTimeout(() => { 51 | self.callback(null, fakeProgress); 52 | }, 500); 53 | }, 54 | 'value is 0': function (fakeProgress) { 55 | assert.equal(fakeProgress.progress, 0); 56 | fakeProgress.stop(); 57 | } 58 | }, 59 | 'fakeProgress instance': { 60 | topic: function () { 61 | return new FakeProgress({ 62 | timeConstant: 5000 63 | }); 64 | }, 65 | 'start and wait timeConstant': { 66 | topic: function (fakeProgress) { 67 | const self = this; 68 | setTimeout(() => { 69 | self.callback(null, fakeProgress); 70 | }, 5000); 71 | fakeProgress.start(); 72 | }, 73 | 'value is around 1 - Math.exp(-1)': function (fakeProgress) { 74 | const expected = 1 - Math.exp(-1); 75 | 76 | assert(fakeProgress.progress > expected - tolerance, 'fakeProgress.progress must be > ' + (expected - tolerance) + ' and is ' + fakeProgress.progress); 77 | assert(fakeProgress.progress < expected + tolerance, 'fakeProgress.progress must be < ' + (expected + tolerance) + ' and is ' + fakeProgress.progress); 78 | }, 79 | 'and wait timeConstant again': { 80 | topic: function (fakeProgress) { 81 | const self = this; 82 | setTimeout(() => { 83 | self.callback(null, fakeProgress); 84 | }, 10000); 85 | }, 86 | 'value is around Math.exp(-3)': function (fakeProgress) { 87 | const expected = 1 - Math.exp(-3); 88 | assert(fakeProgress.progress > expected - tolerance, 'fakeProgress.progress must be > ' + (expected - tolerance) + ' and is ' + fakeProgress.progress); 89 | assert(fakeProgress.progress < expected + tolerance, 'fakeProgress.progress must be < ' + (expected + tolerance) + ' and is ' + fakeProgress.progress); 90 | }, 91 | 'then end': { 92 | topic: function (fakeProgress) { 93 | fakeProgress.end(); 94 | return fakeProgress; 95 | }, 96 | 'value is 1': function (fakeProgress) { 97 | assert.equal(fakeProgress.progress, 1); 98 | } 99 | } 100 | } 101 | } 102 | } 103 | }).addBatch({ 104 | 'sub Tasks example': { 105 | topic: function () { 106 | const callback = this.callback; 107 | 108 | const a = function (cb) { 109 | setTimeout(() => { 110 | cb(); 111 | }, 1000); 112 | }; 113 | 114 | const c = function (cb) { 115 | setTimeout(() => { 116 | cb(); 117 | }, 3000); 118 | }; 119 | 120 | const B = function () { 121 | EventEmitter.call(this); 122 | 123 | let count = 0; 124 | const self = this; 125 | const totalCount = 30; 126 | self.emit('start', count / totalCount); 127 | self._intervalId = setInterval(() => { 128 | count++; 129 | if (count >= totalCount) { 130 | self.emit('end', count / totalCount); 131 | clearInterval(self._intervalId); 132 | } else { 133 | self.emit('progress', count / totalCount); 134 | } 135 | }, 100); 136 | }; 137 | 138 | util.inherits(B, EventEmitter); 139 | 140 | const p = new FakeProgress({}); 141 | 142 | const aProgress = p.createSubProgress({ 143 | timeConstant: 500, 144 | end: 0.3, 145 | autoStart: true 146 | }); 147 | 148 | a(err => { 149 | if (err) { 150 | throw (err); 151 | } 152 | aProgress.stop(); 153 | const bProgress = p.createSubProgress({ 154 | end: 0.8 155 | }); 156 | const b = new B(); 157 | 158 | b.on('progress', progress => { 159 | bProgress.setProgress(progress); 160 | }); 161 | 162 | b.on('end', () => { 163 | bProgress.stop(); 164 | const cProgress = p.createSubProgress({ 165 | timeConstant: 1000, 166 | autoStart: true 167 | }); 168 | c(() => { 169 | cProgress.end(); 170 | }); 171 | }); 172 | }); 173 | 174 | callback(null, p); 175 | }, 176 | 'can be started': function (fakeProgress) { 177 | assert.equal(typeof (fakeProgress.start), 'function'); 178 | }, 179 | 'has a progress over time': function (fakeProgress) { 180 | assert.equal(typeof (fakeProgress.progress), 'number'); 181 | }, 182 | 'can be ended': function (fakeProgress) { 183 | assert.equal(typeof (fakeProgress.end), 'function'); 184 | }, 185 | 'progress is smooth': { 186 | topic: function (fakeProgress) { 187 | setTimeout(this.callback.bind(this, null, fakeProgress), 3000); 188 | }, 189 | 'progress is smooth': function (fakeProgress) { 190 | const expected1 = (1 - Math.exp(-1000 / 500)) * 0.3; 191 | const expected = ((0.8 - expected1) * 2 / 3) + expected1; 192 | 193 | assert(fakeProgress.progress > expected - tolerance, 'fakeProgress.progress must be > ' + (expected - tolerance) + ' and is ' + fakeProgress.progress); 194 | assert(fakeProgress.progress < expected + tolerance, 'fakeProgress.progress must be < ' + (expected + tolerance) + ' and is ' + fakeProgress.progress); 195 | } 196 | } 197 | } 198 | }).run({reporter: 'spec'}, res => { 199 | if (res.honored !== res.total) { 200 | process.exit(1); // eslint-disable-line unicorn/no-process-exit 201 | } 202 | process.exit(0);// eslint-disable-line unicorn/no-process-exit 203 | }); 204 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | interface Options { 2 | timeConstant: number; 3 | autoStart?: boolean; 4 | parent?: FakeProgress; 5 | parentStart?: number; 6 | parentEnd?: number; 7 | } 8 | 9 | declare module 'fake-progress' { 10 | export = FakeProgress; 11 | 12 | class FakeProgress { 13 | constructor(opts: Options); 14 | 15 | progress: number; 16 | 17 | createSubProgress(opts: Options): FakeProgress; 18 | 19 | end(): void; 20 | 21 | setProgress(progress: number): void; 22 | 23 | start(): void; 24 | 25 | stop(): void; 26 | } 27 | } 28 | --------------------------------------------------------------------------------