├── .eslintignore ├── src ├── stop-iteration.js ├── banner.js ├── util.js ├── next-tick.js ├── iterator.js ├── iterate.js └── index.js ├── .npmignore ├── babel.config.js ├── .travis.yml ├── test ├── async-await.spec.js ├── chillout.spec.js ├── until.spec.js ├── repeat.spec.js ├── wait-until.spec.js ├── for-of.spec.js ├── for-each.spec.js └── async-await-test.js ├── .gitignore ├── .eslintrc.json ├── LICENSE ├── examples ├── map-iterator.js └── reduce-iterator.js ├── karma.conf.js ├── package.json ├── CHANGELOG.md ├── benchmark └── benchmark.js ├── dist ├── chillout.min.js └── chillout.js ├── README-ja.md └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/* 2 | local-test/* 3 | examples/* 4 | -------------------------------------------------------------------------------- /src/stop-iteration.js: -------------------------------------------------------------------------------- 1 | const StopIteration = {}; 2 | module.exports = StopIteration; 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | **/.* 2 | docs/ 3 | bin/ 4 | bower_components/ 5 | node_modules/ 6 | tasks/ 7 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ["@babel/preset-env"] 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /src/banner.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * <%= pkg.name %> v<%= pkg.version %> - <%= pkg.description %> 3 | * Copyright (c) 2017-2021 <%= pkg.author %> 4 | * <%= pkg.homepage %> 5 | * @license <%= pkg.license %> 6 | */ 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | script: "npm run travis" 3 | node_js: 4 | - node 5 | - "15" 6 | - "14" 7 | - "13" 8 | - "12" 9 | - "11" 10 | - "10" 11 | addons: 12 | chrome: stable 13 | services: 14 | - xvfb 15 | -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | exports.isThenable = function isThenable(x) { 2 | return x != null && typeof x.then === 'function'; 3 | }; 4 | 5 | exports.isArrayLike = function isArrayLike(x) { 6 | return x != null && typeof x.length === 'number' && x.length >= 0; 7 | }; 8 | -------------------------------------------------------------------------------- /test/async-await.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | if (!isAsyncSupported()) { 4 | it('skipped tests: async/await is not supported in this environment', () => { 5 | assert(true); 6 | }); 7 | } else { 8 | require('./async-await-test'); 9 | } 10 | 11 | function isAsyncSupported() { 12 | try { 13 | eval('async () => {}'); 14 | } catch (e) { 15 | if (e instanceof SyntaxError) { 16 | return false; 17 | } else { 18 | // throws CSP error 19 | return true; 20 | } 21 | } 22 | return true; 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | out 30 | local-test 31 | -------------------------------------------------------------------------------- /test/chillout.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const chillout = require('../src/index'); 3 | 4 | describe('chillout', () => { 5 | it('should have valid main iteration methods', () => { 6 | assert(typeof chillout.forEach === 'function'); 7 | assert(typeof chillout.repeat === 'function'); 8 | assert(typeof chillout.until === 'function'); 9 | assert(typeof chillout.waitUntil === 'function'); 10 | assert(typeof chillout.forOf === 'function'); 11 | }); 12 | 13 | it('should have valid methods and properties', () => { 14 | assert(typeof chillout.StopIteration === 'object'); 15 | assert(typeof chillout.iterate === 'function'); 16 | assert(typeof chillout.iterator === 'object'); 17 | assert(typeof chillout.isThenable === 'function'); 18 | assert(typeof chillout.isArrayLike === 'function'); 19 | assert(typeof chillout.nextTick === 'function'); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/next-tick.js: -------------------------------------------------------------------------------- 1 | const nextTick = (() => { 2 | if (typeof setImmediate === 'function') { 3 | return task => { 4 | setImmediate(task); 5 | }; 6 | } 7 | 8 | if (typeof process === 'object' && typeof process.nextTick === 'function') { 9 | return task => { 10 | process.nextTick(task); 11 | }; 12 | } 13 | 14 | if (typeof MessageChannel === 'function') { 15 | // http://www.nonblocking.io/2011/06/windownexttick.html 16 | const channel = new MessageChannel(); 17 | let head = {}; 18 | let tail = head; 19 | 20 | channel.port1.onmessage = () => { 21 | head = head.next; 22 | const task = head.task; 23 | delete head.task; 24 | task(); 25 | }; 26 | 27 | return task => { 28 | tail = tail.next = { task }; 29 | channel.port2.postMessage(0); 30 | }; 31 | } 32 | 33 | return task => { 34 | setTimeout(task, 0); 35 | }; 36 | })(); 37 | 38 | module.exports = nextTick; 39 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "parserOptions": { 4 | "ecmaVersion": 2020, 5 | "sourceType": "module", 6 | "ecmaFeatures": { 7 | "globalReturn": true, 8 | "impliedStrict": true, 9 | "experimentalObjectRestSpread": true 10 | } 11 | }, 12 | "env": { 13 | "es6": true, 14 | "browser": true, 15 | "node": true, 16 | "shared-node-browser": true, 17 | "worker": true, 18 | "mocha": true 19 | }, 20 | "rules": { 21 | "comma-dangle": "off", 22 | "key-spacing": [ 23 | "error", { 24 | "beforeColon": false, 25 | "afterColon": true 26 | } 27 | ], 28 | "no-empty": "off", 29 | "no-mixed-spaces-and-tabs": "error", 30 | "no-unused-vars": [ 31 | "warn", { 32 | "args": "none" 33 | } 34 | ], 35 | "no-tabs": "error", 36 | "no-useless-escape": "warn", 37 | "no-console": "warn", 38 | "no-undef": "warn", 39 | "semi": "warn" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2021 Polygon Planet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /test/until.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const chillout = require('../src/index'); 3 | 4 | describe('until', () => { 5 | it('10 times', done => { 6 | let i = 0; 7 | chillout.until(() => { 8 | if (++i === 10) { 9 | return chillout.StopIteration; 10 | } 11 | }).then(() => { 12 | assert(i === 10); 13 | done(); 14 | }); 15 | }); 16 | 17 | it('10 times with context', done => { 18 | const context = { i: 0 }; 19 | chillout.until(function() { 20 | if (++this.i === 10) { 21 | return chillout.StopIteration; 22 | } 23 | }, context).then(() => { 24 | assert(context.i === 10); 25 | done(); 26 | }); 27 | }); 28 | 29 | it('stop nested Promise iteration', done => { 30 | let i = 0; 31 | chillout.until(() => { 32 | if (++i === 10) { 33 | return new Promise((resolve, reject) => { 34 | resolve(chillout.StopIteration); 35 | }); 36 | } 37 | }).then(() => { 38 | assert(i === 10); 39 | done(); 40 | }); 41 | }); 42 | 43 | it('throw an error', done => { 44 | chillout.until(() => { 45 | throw 'ok'; 46 | }).then(() => { 47 | throw 'error'; 48 | }).catch(e => { 49 | assert.equal(e, 'ok'); 50 | done(); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /examples/map-iterator.js: -------------------------------------------------------------------------------- 1 | // Example for defining "map", extend chillout.js iterations 2 | // Reference: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/map 3 | 4 | function mapIterator(array, callback, context) { 5 | var len = array.length; 6 | var i = 0; 7 | var items = []; 8 | 9 | return { 10 | next() { 11 | var isStop = false; 12 | if (i >= len) { 13 | isStop = true; 14 | return [isStop, items]; 15 | } 16 | items[i] = callback.call(context, array[i], i, array); 17 | i++; 18 | return [isStop, items]; 19 | } 20 | }; 21 | }; 22 | 23 | chillout.map = function(/* array, callback, context */) { 24 | return chillout.iterate(mapIterator.apply(this, arguments)); 25 | }; 26 | 27 | 28 | // example 29 | chillout.map([0, 1, 2, 3], function(num) { 30 | return num * 10; 31 | }).then(function(items) { 32 | console.log(items); // [0, 10, 20, 30] 33 | }); 34 | 35 | 36 | chillout.map(['1', '2', '3', '4', '5'], Number).then(function(items) { 37 | console.log(items); // [1, 2, 3, 4, 5] 38 | }); 39 | 40 | 41 | var users = [ 42 | { id: 1, name: 'foo' }, 43 | { id: 2, name: 'bar' }, 44 | { id: 3, name: 'baz' } 45 | ]; 46 | 47 | chillout.map(users, user => user.name).then(function(names) { 48 | console.log(names); // ['foo', 'bar', 'baz'] 49 | }); 50 | -------------------------------------------------------------------------------- /examples/reduce-iterator.js: -------------------------------------------------------------------------------- 1 | // Example for defining "reduce", extend chillout.js iterations 2 | // Reference: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce 3 | 4 | function reduceIterator(array, callback /*, initialValue, context */) { 5 | var len = array.length; 6 | var i = 0; 7 | var value, context; 8 | 9 | if (arguments.length >= 3) { 10 | value = arguments[2]; 11 | context = arguments[3]; 12 | } else { 13 | value = array[0]; 14 | context = null; 15 | i++; 16 | } 17 | 18 | return { 19 | next() { 20 | var isStop = false; 21 | if (i >= len) { 22 | isStop = true; 23 | return [isStop, value]; 24 | } 25 | value = callback.call(context, value, array[i], i, array); 26 | i++; 27 | return [isStop, value]; 28 | } 29 | }; 30 | }; 31 | 32 | chillout.reduce = function(/* array, callback, initialValue, context */) { 33 | return chillout.iterate(reduceIterator.apply(this, arguments)); 34 | }; 35 | 36 | 37 | // example 38 | chillout.reduce([0, 1, 2, 3], function(accumulator, currentValue) { 39 | return accumulator + currentValue; 40 | }, 0).then(function(sum) { 41 | console.log(sum); // 6 42 | }); 43 | 44 | 45 | chillout.reduce([1, 2, 3, 4], function(accumulator, currentValue) { 46 | return accumulator + currentValue; 47 | }).then(function(sum) { 48 | console.log(sum); // 10 49 | }); 50 | 51 | 52 | chillout.reduce([[0, 1], [2, 3], [4, 5]], function(accumulator, currentValue) { 53 | return accumulator.concat(currentValue); 54 | }, []).then(function(flattened) { 55 | console.log(flattened); // [0, 1, 2, 3, 4, 5] 56 | }); 57 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | basePath: '', 4 | frameworks: [ 5 | 'es6-shim', 6 | 'mocha', 7 | 'browserify', 8 | 'detectBrowsers' 9 | ], 10 | files: [ 11 | './test/**/*.spec.js' 12 | ], 13 | exclude: [ 14 | './test/async-await.spec.js' 15 | ], 16 | preprocessors: { 17 | './test/**/*.js': ['browserify'] 18 | }, 19 | client: { 20 | mocha: { 21 | reporter: 'html', 22 | ui: 'bdd' 23 | } 24 | }, 25 | reporters: ['mocha'], 26 | port: 9876, 27 | colors: true, 28 | // level of logging 29 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 30 | logLevel: config.LOG_INFO, 31 | autoWatch: false, 32 | customLaunchers: { 33 | // Custom launchers for Travis 34 | Chrome_travis_ci: { 35 | base: 'Chrome', 36 | flags: ['--no-sandbox'] 37 | } 38 | }, 39 | detectBrowsers: { 40 | enabled: true, 41 | usePhantomJS: false, 42 | postDetection: function(availableBrowser) { 43 | if (process.env.TRAVIS) { 44 | return ['Chrome_travis_ci']; 45 | } 46 | return availableBrowser; 47 | } 48 | }, 49 | plugins: [ 50 | 'karma-browserify', 51 | 'karma-es6-shim', 52 | 'karma-mocha', 53 | 'karma-mocha-reporter', 54 | 'karma-chrome-launcher', 55 | 'karma-firefox-launcher', 56 | 'karma-ie-launcher', 57 | 'karma-safari-launcher', 58 | 'karma-detect-browsers' 59 | ], 60 | singleRun: true, 61 | concurrency: Infinity 62 | }); 63 | }; 64 | -------------------------------------------------------------------------------- /test/repeat.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const chillout = require('../src/index'); 3 | 4 | describe('repeat', () => { 5 | it('specify number', done => { 6 | let n = 0; 7 | chillout.repeat(5, i => { 8 | assert(n++ === i); 9 | }).then(() => { 10 | assert(n === 5); 11 | done(); 12 | }); 13 | }); 14 | 15 | it('specify object', done => { 16 | let n = 10; 17 | chillout.repeat({ start: 10, step: 2, done: 20 }, i => { 18 | assert(n === i); 19 | n += 2; 20 | }).then(() => { 21 | assert(n === 20); 22 | done(); 23 | }); 24 | }); 25 | 26 | it('specify context', done => { 27 | const context = { 28 | n: 0 29 | }; 30 | chillout.repeat(5, function(i) { 31 | assert(this.n++ === i); 32 | }, context).then(() => { 33 | assert(context.n === 5); 34 | done(); 35 | }); 36 | }); 37 | 38 | it('stop iteration', done => { 39 | let n = 0; 40 | chillout.repeat(5, i => { 41 | assert(n++ === i); 42 | if (n === 3) { 43 | return chillout.StopIteration; 44 | } 45 | }).then(() => { 46 | assert(n === 3); 47 | done(); 48 | }); 49 | }); 50 | 51 | it('stop nested Promise iteration', done => { 52 | let n = 0; 53 | chillout.repeat(5, i => { 54 | assert(n++ === i); 55 | if (n === 3) { 56 | return new Promise((resolve, reject) => { 57 | resolve(chillout.StopIteration); 58 | }); 59 | } 60 | }).then(() => { 61 | assert(n === 3); 62 | done(); 63 | }); 64 | }); 65 | 66 | it('throw an error', done => { 67 | chillout.repeat(5, i => { 68 | throw 'ok'; 69 | }).then(() => { 70 | throw 'error'; 71 | }).catch(e => { 72 | assert.equal(e, 'ok'); 73 | done(); 74 | }); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /src/iterator.js: -------------------------------------------------------------------------------- 1 | const { isArrayLike } = require('./util'); 2 | 3 | exports.forEach = function(obj, callback, context) { 4 | let i = 0; 5 | let len; 6 | 7 | if (isArrayLike(obj)) { 8 | len = obj.length; 9 | 10 | return { 11 | next() { 12 | if (i >= len) { 13 | return [true, null]; 14 | } 15 | 16 | const value = callback.call(context, obj[i], i, obj); 17 | i++; 18 | return [false, value]; 19 | } 20 | }; 21 | } 22 | 23 | const keys = Object.keys(obj); 24 | len = keys.length; 25 | 26 | return { 27 | next() { 28 | if (i >= len) { 29 | return [true, null]; 30 | } 31 | 32 | const key = keys[i++]; 33 | const value = callback.call(context, obj[key], key, obj); 34 | return [false, value]; 35 | } 36 | }; 37 | }; 38 | 39 | exports.repeat = function(count, callback, context) { 40 | let i; 41 | let step; 42 | let done; 43 | 44 | if (count && typeof count === 'object') { 45 | i = count.start || 0; 46 | step = count.step || 1; 47 | done = count.done; 48 | } else { 49 | i = 0; 50 | step = 1; 51 | done = count; 52 | } 53 | 54 | return { 55 | next() { 56 | const value = callback.call(context, i); 57 | 58 | i += step; 59 | if (i >= done) { 60 | return [true, value]; 61 | } 62 | return [false, value]; 63 | } 64 | }; 65 | }; 66 | 67 | exports.until = function(callback, context) { 68 | return { 69 | next() { 70 | const value = callback.call(context); 71 | return [false, value]; 72 | } 73 | }; 74 | }; 75 | 76 | exports.forOf = function(iterable, callback, context) { 77 | const it = iterable[Symbol.iterator](); 78 | 79 | return { 80 | next() { 81 | const nextIterator = it.next(); 82 | 83 | if (nextIterator.done) { 84 | return [true, null]; 85 | } 86 | const value = callback.call(context, nextIterator.value, iterable); 87 | return [false, value]; 88 | } 89 | }; 90 | }; 91 | -------------------------------------------------------------------------------- /test/wait-until.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const chillout = require('../src/index'); 3 | 4 | describe('waitUntil', () => { 5 | it('should be executed slowly than chillout.until', done => { 6 | let elapsedTime_until = 0; 7 | let elapsedTime_waitUntil = 0; 8 | 9 | function run_until() { 10 | return new Promise((resolve, reject) => { 11 | const startTime = Date.now(); 12 | let i = 0; 13 | chillout.until(() => { 14 | if (++i === 10) { 15 | elapsedTime_until = Date.now() - startTime; 16 | return chillout.StopIteration; 17 | } 18 | }).then(() => { 19 | resolve(); 20 | }); 21 | }); 22 | } 23 | 24 | function run_waitUntil() { 25 | return new Promise((resolve, reject) => { 26 | const startTime = Date.now(); 27 | let i = 0; 28 | chillout.waitUntil(() => { 29 | if (++i === 10) { 30 | elapsedTime_waitUntil = Date.now() - startTime; 31 | return chillout.StopIteration; 32 | } 33 | }).then(() => { 34 | resolve(); 35 | }); 36 | }); 37 | } 38 | 39 | Promise.all([run_until(), run_waitUntil()]).then(() => { 40 | assert(elapsedTime_until < elapsedTime_waitUntil); 41 | done(); 42 | }); 43 | }); 44 | 45 | it('10 times', done => { 46 | let i = 0; 47 | chillout.waitUntil(() => { 48 | if (++i === 10) { 49 | return chillout.StopIteration; 50 | } 51 | }).then(() => { 52 | assert(i === 10); 53 | done(); 54 | }); 55 | }); 56 | 57 | it('10 times with context', done => { 58 | const context = { i: 0 }; 59 | chillout.waitUntil(function() { 60 | if (++this.i === 10) { 61 | return chillout.StopIteration; 62 | } 63 | }, context).then(() => { 64 | assert(context.i === 10); 65 | done(); 66 | }); 67 | }); 68 | 69 | it('stop nested Promise iteration', done => { 70 | let i = 0; 71 | chillout.waitUntil(() => { 72 | if (++i === 10) { 73 | return new Promise((resolve, reject) => { 74 | resolve(chillout.StopIteration); 75 | }); 76 | } 77 | }).then(() => { 78 | assert(i === 10); 79 | done(); 80 | }); 81 | }); 82 | 83 | it('throw an error', done => { 84 | chillout.waitUntil(() => { 85 | throw 'ok'; 86 | }).then(() => { 87 | throw 'error'; 88 | }).catch(e => { 89 | assert.equal(e, 'ok'); 90 | done(); 91 | }); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /src/iterate.js: -------------------------------------------------------------------------------- 1 | const { isThenable } = require('./util'); 2 | const StopIteration = require('./stop-iteration'); 3 | const nextTick = require('./next-tick'); 4 | 5 | const MAX_DELAY = 1500; 6 | 7 | module.exports = function iterate(it, interval = 0) { 8 | return new Promise((resolve, reject) => { 9 | let totalTime = 0; 10 | 11 | function doIterate() { 12 | const cycleStartTime = Date.now(); 13 | let cycleEndTime; 14 | 15 | try { 16 | for (;;) { 17 | const [isStop, value] = it.next(); 18 | 19 | if (isThenable(value)) { 20 | value.then(awaitedValue => { 21 | if (isStop) { 22 | resolve(awaitedValue); 23 | } else if (awaitedValue === StopIteration) { 24 | resolve(); 25 | } else { 26 | doIterate(); 27 | } 28 | }, reject); 29 | return; 30 | } 31 | 32 | if (isStop) { 33 | resolve(value); 34 | return; 35 | } 36 | if (value === StopIteration) { 37 | resolve(); 38 | return; 39 | } 40 | 41 | if (interval > 0) { 42 | break; 43 | } 44 | 45 | const endTime = Date.now(); 46 | cycleEndTime = endTime - cycleStartTime; 47 | totalTime += cycleEndTime; 48 | 49 | // Break the loop when the process is continued for more than 1s 50 | if (totalTime > 1000) { 51 | break; 52 | } 53 | 54 | // Delay is not required for fast iteration 55 | if (cycleEndTime < 10) { 56 | continue; 57 | } 58 | break; 59 | } 60 | } catch (e) { 61 | reject(e); 62 | return; 63 | } 64 | 65 | if (interval > 0) { 66 | // Short timeouts will throttled to >=4ms by the browser, so we execute tasks 67 | // slowly enough to reduce CPU load 68 | const delay = Math.min(MAX_DELAY, Date.now() - cycleStartTime + interval); 69 | setTimeout(doIterate, delay); 70 | } else { 71 | // Add delay corresponding to the processing speed 72 | const delay = Math.min(MAX_DELAY, cycleEndTime / 3); 73 | totalTime = 0; 74 | 75 | if (delay > 10) { 76 | setTimeout(doIterate, delay); 77 | } else { 78 | nextTick(doIterate); 79 | } 80 | } 81 | } 82 | 83 | // The first call doesn't need to wait, so it will execute a task immediately 84 | nextTick(doIterate); 85 | }); 86 | }; 87 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chillout", 3 | "description": "Reduce CPU usage by non-blocking async loop and psychologically speed up in JavaScript", 4 | "version": "5.0.0", 5 | "author": "polygon planet ", 6 | "bugs": { 7 | "url": "https://github.com/polygonplanet/chillout/issues" 8 | }, 9 | "devDependencies": { 10 | "@babel/core": "^7.12.10", 11 | "@babel/preset-env": "^7.12.11", 12 | "babelify": "^10.0.0", 13 | "bannerify": "^1.0.1", 14 | "browserify": "^17.0.0", 15 | "es6-shim": "^0.35.6", 16 | "eslint": "^7.17.0", 17 | "karma": "^5.2.3", 18 | "karma-browserify": "^8.0.0", 19 | "karma-chrome-launcher": "^3.1.0", 20 | "karma-detect-browsers": "^2.3.3", 21 | "karma-es6-shim": "^1.0.0", 22 | "karma-firefox-launcher": "^2.1.0", 23 | "karma-ie-launcher": "^1.0.0", 24 | "karma-mocha": "^2.0.1", 25 | "karma-mocha-reporter": "^2.2.5", 26 | "karma-safari-launcher": "^1.0.0", 27 | "mocha": "^8.2.1", 28 | "package-json-versionify": "^1.0.4", 29 | "pidusage": "1.0.2", 30 | "power-assert": "^1.6.1", 31 | "uglify-js": "^3.12.4", 32 | "uglifyify": "^5.0.2", 33 | "watchify": "^3.11.1" 34 | }, 35 | "engines": { 36 | "node": ">=8.10.0" 37 | }, 38 | "files": [ 39 | "dist/*", 40 | "src/*" 41 | ], 42 | "homepage": "https://github.com/polygonplanet/chillout", 43 | "keywords": [ 44 | "acceleration", 45 | "async await", 46 | "asynchronous", 47 | "cpu", 48 | "iteration", 49 | "lightweight", 50 | "non-blocking", 51 | "optimization", 52 | "performance" 53 | ], 54 | "license": "MIT", 55 | "main": "src/index.js", 56 | "repository": { 57 | "type": "git", 58 | "url": "https://github.com/polygonplanet/chillout.git" 59 | }, 60 | "scripts": { 61 | "benchmark": "node benchmark/benchmark", 62 | "build": "npm run compile && npm run minify", 63 | "compile": "browserify src/index.js -o dist/chillout.js -s chillout -p [ bannerify --file src/banner.js ] --no-bundle-external --bare", 64 | "minify": "uglifyjs dist/chillout.js -o dist/chillout.min.js --comments -c -m -b ascii_only=true,beautify=false", 65 | "test": "./node_modules/.bin/eslint . && npm run build && mocha test/**/*.spec.js --timeout 10000 && karma start karma.conf.js", 66 | "travis": "npm run build && mocha test/**/*.spec.js --timeout 10000 && karma start karma.conf.js --single-run", 67 | "watch": "watchify src/index.js -o dist/chillout.js -s chillout -p [ bannerify --file src/banner.js ] --no-bundle-external --bare --poll=200 -v" 68 | }, 69 | "browserify": { 70 | "transform": [ 71 | "babelify", 72 | "package-json-versionify" 73 | ] 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /test/for-of.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const chillout = require('../src/index'); 3 | 4 | describe('forOf', () => { 5 | if (typeof Symbol === 'undefined') { 6 | return it('skip', () => { 7 | assert(true); 8 | }); 9 | } 10 | 11 | it('array', done => { 12 | const values = []; 13 | chillout.forOf([1, 2, 3], value => { 14 | values.push(value); 15 | }).then(() => { 16 | assert.deepEqual(values, [1, 2, 3]); 17 | done(); 18 | }); 19 | }); 20 | 21 | it('string', done => { 22 | const values = []; 23 | chillout.forOf('abc', value => { 24 | values.push(value); 25 | }).then(() => { 26 | assert.deepEqual(values, ['a', 'b', 'c']); 27 | done(); 28 | }); 29 | }); 30 | 31 | it('TypedArray', done => { 32 | const values = []; 33 | const iterable = new Uint8Array([0x00, 0xff]); 34 | 35 | if (typeof iterable[Symbol.iterator] !== 'function') { 36 | // skip 37 | assert(true); 38 | done(); 39 | return; 40 | } 41 | 42 | chillout.forOf(iterable, value => { 43 | values.push(value); 44 | }).then(() => { 45 | assert.deepEqual(values, [0, 255]); 46 | done(); 47 | }); 48 | }); 49 | 50 | it('Map', done => { 51 | const values = []; 52 | const iterable = new Map([['a', 1], ['b', 2], ['c', 3]]); 53 | chillout.forOf(iterable, value => { 54 | values.push(value); 55 | }).then(() => { 56 | assert.deepEqual(values, [['a', 1], ['b', 2], ['c', 3]]); 57 | done(); 58 | }); 59 | }); 60 | 61 | it('Set', done => { 62 | const values = []; 63 | const iterable = new Set([1, 1, 2, 2, 3, 3]); 64 | chillout.forOf(iterable, value => { 65 | values.push(value); 66 | }).then(() => { 67 | assert.deepEqual(values, [1, 2, 3]); 68 | done(); 69 | }); 70 | }); 71 | 72 | it('specify context', done => { 73 | const context = { 74 | values: [] 75 | }; 76 | chillout.forOf([1, 2, 3], function(value) { 77 | this.values.push(value); 78 | }, context).then(() => { 79 | assert.deepEqual(context.values, [1, 2, 3]); 80 | done(); 81 | }); 82 | }); 83 | 84 | it('stop iteration', done => { 85 | const values = []; 86 | chillout.forOf([1, 2, 3, 4, 5], value => { 87 | values.push(value); 88 | if (value === 3) { 89 | return chillout.StopIteration; 90 | } 91 | }).then(() => { 92 | assert.deepEqual(values, [1, 2, 3]); 93 | done(); 94 | }); 95 | }); 96 | 97 | it('stop nested Promise iteration', done => { 98 | const values = []; 99 | chillout.forOf([1, 2, 3, 4, 5], value => { 100 | values.push(value); 101 | if (value === 3) { 102 | return new Promise((resolve, reject) => { 103 | resolve(chillout.StopIteration); 104 | }); 105 | } 106 | }).then(() => { 107 | assert.deepEqual(values, [1, 2, 3]); 108 | done(); 109 | }); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## [5.0.0] - 2020-01-24 6 | ### Changed 7 | - Changed delay time to run more slowly to reduce CPU than previously. 8 | 9 | ### Removed 10 | - Drop bower support. 11 | 12 | ## [4.0.4] - 2019-11-02 13 | ### Fixed 14 | - Bump dev dependencies. 15 | 16 | ## [4.0.3] - 2019-08-18 17 | ### Fixed 18 | - Fixed invalid uglifyjs option. 19 | 20 | ## [4.0.2] - 2019-03-18 21 | ### Fixed 22 | - Fixed tests timeout. 23 | 24 | ## [4.0.0] - 2019-03-03 25 | ### Added 26 | - Added `waitUntil` method. 27 | - Added core methods and properties to exports. 28 | 29 | ### Changed 30 | - Changed `till` method name to `until` 31 | - Changed the key name to `done` instead of `end` in `repeat` method with arguments passed as object. 32 | - Changed use `return chillout.StopIteration` to stop the loop instead of `return false`. 33 | 34 | ### Fixed 35 | - Fixed parameters can be passed when called with nested Promise. 36 | 37 | ## [3.1.9] - 2018-12-10 38 | ### Fixed 39 | - Fixed `can't find module` using "require". 40 | 41 | ## [3.1.8] - 2018-12-10 42 | ### Fixed 43 | - Fixed result when using `async/await`. 44 | 45 | ### Changed 46 | - Change internal iteration stopper. 47 | 48 | ## [3.1.7] - 2018-05-10 49 | ### Fixed 50 | - Fixed bug for `can't find module`. 51 | 52 | ## [3.1.6] - 2018-02-17 53 | ### Fixed 54 | - Fixed build commands. 55 | 56 | ## [3.1.4] - 2017-07-15 57 | ### Changed 58 | - Change eslint base to `eslint:recommended`. 59 | 60 | ## [3.1.3] - 2017-01-20 61 | ### Fixed 62 | - Improve return statement in iterator function. Thanks to [@JuhQ](https://github.com/JuhQ). 63 | 64 | ## [3.1.0] - 2016-06-06 65 | ### Added 66 | - Add forOf iterator. 67 | 68 | ## [3.0.0] - 2016-04-30 69 | ### Changed 70 | - Change API method names. 71 | 72 | ## [2.0.0] - 2016-03-12 73 | ### Changed 74 | - Modularize the code base 75 | 76 | ## [1.1.5] - 2016-02-12 77 | ### Fixed 78 | - Fix chillout.each to work with generic array-like object instead of an array. 79 | 80 | ## [1.1.0] - 2016-01-15 81 | ### Fixed 82 | - Fix setImmediate by MessageChannel. 83 | 84 | ## [1.0.0] - 2016-01-05 85 | ### Added 86 | - First release. 87 | 88 | [5.0.0]: https://github.com/polygonplanet/chillout/compare/4.0.3...5.0.0 89 | [4.0.4]: https://github.com/polygonplanet/chillout/compare/4.0.3...4.0.4 90 | [4.0.3]: https://github.com/polygonplanet/chillout/compare/4.0.2...4.0.3 91 | [4.0.2]: https://github.com/polygonplanet/chillout/compare/4.0.0...4.0.2 92 | [4.0.0]: https://github.com/polygonplanet/chillout/compare/3.1.9...4.0.0 93 | [3.1.9]: https://github.com/polygonplanet/chillout/compare/3.1.8...3.1.9 94 | [3.1.8]: https://github.com/polygonplanet/chillout/compare/3.1.7...3.1.8 95 | [3.1.7]: https://github.com/polygonplanet/chillout/compare/3.1.6...3.1.7 96 | [3.1.6]: https://github.com/polygonplanet/chillout/compare/3.1.4...3.1.6 97 | [3.1.4]: https://github.com/polygonplanet/chillout/compare/3.1.3...3.1.4 98 | [3.1.3]: https://github.com/polygonplanet/chillout/compare/3.1.0...3.1.3 99 | [3.1.0]: https://github.com/polygonplanet/chillout/compare/3.0.0...3.1.0 100 | [3.0.0]: https://github.com/polygonplanet/chillout/compare/2.0.0...3.0.0 101 | [2.0.0]: https://github.com/polygonplanet/chillout/compare/1.1.5...2.0.0 102 | [1.1.5]: https://github.com/polygonplanet/chillout/compare/1.1.0...1.1.5 103 | [1.1.0]: https://github.com/polygonplanet/chillout/compare/1.0.0...1.1.0 104 | [1.0.0]: https://github.com/polygonplanet/chillout/releases/tag/1.0.0 105 | -------------------------------------------------------------------------------- /benchmark/benchmark.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console: "off" */ 2 | 'use strict'; 3 | 4 | require('es6-shim'); 5 | var chillout = require('../dist/chillout'); 6 | var pidusage = require('pidusage'); 7 | 8 | var REPEAT_COUNT = 100; 9 | var cpuLoads = { 10 | for_statement: [], 11 | chillout_repeat: [] 12 | }; 13 | 14 | // Get CPU usage for the current node process 15 | function cpuStat(type) { 16 | return new Promise(function(resolve, reject) { 17 | pidusage.stat(process.pid, function(err, stat) { 18 | if (err) { 19 | console.log('[Error]'); 20 | console.log(err); 21 | console.log('*** If you got an error on Windows, you may try succeed if you execute this benchmark several times.'); 22 | process.exit(0); 23 | } 24 | 25 | cpuLoads[type].push(stat.cpu); 26 | resolve(); 27 | }); 28 | }); 29 | } 30 | 31 | // Get CPU usage average 32 | function cpuAvg(type) { 33 | var loads = cpuLoads[type].filter(function(load) { 34 | // Skip 0% load 35 | return load > 0; 36 | }); 37 | 38 | var len = loads.length; 39 | if (len === 0) { 40 | return 'Failed to get CPU usage'; 41 | } 42 | 43 | return (loads.reduce(function(total, load) { 44 | total += load; 45 | return total; 46 | }, 0) / len).toFixed(2) + '%'; 47 | } 48 | 49 | function heavyProcess() { 50 | var v; 51 | for (var i = 0; i < 10000; i++) { 52 | for (var j = 0; j < 10000; j++) { 53 | v = i * j; 54 | } 55 | } 56 | return v; 57 | } 58 | 59 | // Repeat the slow processing by JavaScript ForStatement 60 | function run_for_statement() { 61 | return run('JavaScript ForStatement', 'for_statement', function(stat) { 62 | return new Promise(function(resolve) { 63 | for (var i = 0; i < REPEAT_COUNT; i++) { 64 | heavyProcess(); 65 | stat(); 66 | } 67 | resolve(); 68 | }); 69 | }); 70 | } 71 | 72 | // Repeat the slow processing by using chillout.repeat 73 | function run_chillout_repeat() { 74 | return run('chillout.repeat', 'chillout_repeat', function(stat) { 75 | return chillout.repeat(REPEAT_COUNT, function() { 76 | heavyProcess(); 77 | stat(); 78 | }); 79 | }); 80 | } 81 | 82 | function run(title, type, cycle) { 83 | return new Promise(function(resolve, reject) { 84 | console.log('Repeat the slow processing by using %s:', title); 85 | 86 | var q = []; 87 | var stat = function() { 88 | q.push(cpuStat(type)); 89 | }; 90 | 91 | var time = Date.now(); 92 | cycle(stat).then(function() { 93 | var processingTime = Date.now() - time; 94 | console.log('Repeated %d times', REPEAT_COUNT); 95 | console.log('Processing time: %dms', processingTime); 96 | 97 | Promise.all(q).then(function() { 98 | console.log('CPU usage on Node process (Average): %s', cpuAvg(type)); 99 | cpuLoads[type].length = 0; 100 | q.length = 0; 101 | resolve(); 102 | }); 103 | }).catch(function(err) { 104 | console.error(err); 105 | reject(err); 106 | }); 107 | }); 108 | } 109 | 110 | function wait(ms) { 111 | return new Promise(function(resolve) { 112 | setTimeout(resolve, ms); 113 | }); 114 | } 115 | 116 | console.log('(1) JavaScript for loop'); 117 | wait(3000).then(run_for_statement).then(function() { 118 | console.log('--------------------------------'); 119 | console.log('(2) chillout.repeat'); 120 | // Wait a little bit for cooling the CPU 121 | wait(3000).then(run_chillout_repeat).then(function() { 122 | process.exit(0); 123 | }); 124 | }).catch(function(e) { 125 | console.log(e); 126 | process.exit(0); 127 | }); 128 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const iterator = require('./iterator'); 2 | const iterate = require('./iterate'); 3 | const { isThenable, isArrayLike } = require('./util'); 4 | const nextTick = require('./next-tick'); 5 | const StopIteration = require('./stop-iteration'); 6 | 7 | exports.version = require('../package.json').version; 8 | 9 | /** 10 | * Executes a provided function once per array or object element. 11 | * The iteration will break if the callback function returns `chillout.StopIteration`, 12 | * or an error occurs. 13 | * This method can be called like JavaScript `Array forEach`. 14 | * 15 | * @param {array|object} obj Target array or object 16 | * @param {function} callback Function to execute for each element, taking three arguments: 17 | * - value: The current element being processed in the array/object 18 | * - key: The key of the current element being processed in the array/object 19 | * - obj: The array/object that `forEach` is being applied to 20 | * @param {object} [context] Value to use as `this` when executing callback 21 | * @return {promise} Return new Promise 22 | */ 23 | exports.forEach = function forEach(obj, callback, context) { 24 | return iterate(iterator.forEach(obj, callback, context)); 25 | }; 26 | 27 | /** 28 | * Executes a provided function the specified number times. 29 | * The iteration will break if the callback function returns `chillout.StopIteration`, 30 | * or an error occurs. 31 | * This method can be called like JavaScript `for` statement. 32 | * 33 | * @param {number|object} count The number of times or object for execute the 34 | * function. Following parameters are available if specify object: 35 | * - start: The number of start 36 | * - step: The number of step 37 | * - end: The number of end 38 | * @param {function} callback Function to execute for each times, taking one argument: 39 | * - i: The current number 40 | * @param {object} [context] Value to use as `this` when executing callback 41 | * @return {promise} Return new Promise 42 | */ 43 | exports.repeat = function repeat(count, callback, context) { 44 | return iterate(iterator.repeat(count, callback, context)); 45 | }; 46 | 47 | /** 48 | * Executes a provided function until the `callback` returns `chillout.StopIteration`, 49 | * or an error occurs. 50 | * This method can be called like JavaScript `while (true) { ... }` statement. 51 | * 52 | * @param {function} callback The function that is executed for each iteration 53 | * @param {object} [context] Value to use as `this` when executing callback 54 | * @return {promise} Return new Promise 55 | */ 56 | exports.until = function until(callback, context) { 57 | return iterate(iterator.until(callback, context)); 58 | }; 59 | 60 | // Minimum setTimeout interval for waitUntil 61 | const WAIT_UNTIL_INTERVAL = 13; 62 | 63 | /** 64 | * Executes a provided function until the `callback` returns `chillout.StopIteration`, 65 | * or an error occurs. 66 | * This method can be called like JavaScript `while (true) { ... }` statement, 67 | * and it works same as `until`, but it executes tasks with more slowly interval 68 | * than `until` to reduce CPU load. 69 | * This method is useful when you want to wait until some processing done. 70 | * 71 | * @param {function} callback The function that is executed for each iteration 72 | * @param {object} [context] Value to use as `this` when executing callback 73 | * @return {promise} Return new Promise 74 | */ 75 | exports.waitUntil = function waitUntil(callback, context) { 76 | return iterate(iterator.until(callback, context), WAIT_UNTIL_INTERVAL); 77 | }; 78 | 79 | /** 80 | * Iterates the iterable objects, similar to the `for-of` statement. 81 | * Executes a provided function once per element. 82 | * The iteration will break if the callback function returns `chillout.StopIteration`, 83 | * or an error occurs. 84 | * 85 | * @param {array|string|object} iterable Target iterable objects 86 | * @param {function} callback Function to execute for each element, taking one argument: 87 | * - value: A value of a property on each iteration 88 | * @param {object} [context] Value to use as `this` when executing callback 89 | * @return {promise} Return new Promise 90 | */ 91 | exports.forOf = function forOf(iterable, callback, context) { 92 | return iterate(iterator.forOf(iterable, callback, context)); 93 | }; 94 | 95 | // If you want to stop the loops, return this StopIteration 96 | // it works like 'break' statement in JavaScript 'for' statement 97 | exports.StopIteration = StopIteration; 98 | 99 | // Exports core methods for user defining other iterations by using chillout 100 | exports.iterate = iterate; 101 | exports.iterator = iterator; 102 | exports.isThenable = isThenable; 103 | exports.isArrayLike = isArrayLike; 104 | exports.nextTick = nextTick; 105 | -------------------------------------------------------------------------------- /test/for-each.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const chillout = require('../src/index'); 3 | 4 | describe('forEach', () => { 5 | it('array', done => { 6 | const values = []; 7 | const keys = []; 8 | chillout.forEach([1, 2, 3], (value, i) => { 9 | values.push(value); 10 | keys.push(i); 11 | }).then(() => { 12 | assert.deepEqual(values, [1, 2, 3]); 13 | assert.deepEqual(keys, [0, 1, 2]); 14 | done(); 15 | }); 16 | }); 17 | 18 | it('object', done => { 19 | const values = []; 20 | const keys = []; 21 | chillout.forEach({ a: 1, b: 2, c: 3 }, (value, key) => { 22 | values.push(value); 23 | keys.push(key); 24 | }).then(() => { 25 | assert.deepEqual(values, [1, 2, 3]); 26 | assert.deepEqual(keys, ['a', 'b', 'c']); 27 | done(); 28 | }); 29 | }); 30 | 31 | it('array-like object', done => { 32 | const values = []; 33 | const keys = []; 34 | chillout.forEach({ 0: 1, 1: 2, 2: 3, length: 3 }, (value, i) => { 35 | values.push(value); 36 | keys.push(i); 37 | }).then(() => { 38 | assert.deepEqual(values, [1, 2, 3]); 39 | assert.deepEqual(keys, [0, 1, 2]); 40 | done(); 41 | }); 42 | }); 43 | 44 | it('array with context', done => { 45 | const context = { 46 | values: [], 47 | keys: [] 48 | }; 49 | chillout.forEach([1, 2, 3], function(value, i) { 50 | this.values.push(value); 51 | this.keys.push(i); 52 | }, context).then(() => { 53 | assert.deepEqual(context.values, [1, 2, 3]); 54 | assert.deepEqual(context.keys, [0, 1, 2]); 55 | done(); 56 | }); 57 | }); 58 | 59 | it('object with context', done => { 60 | const context = { 61 | values: [], 62 | keys: [] 63 | }; 64 | chillout.forEach({ a: 1, b: 2, c: 3 }, function(value, key) { 65 | this.values.push(value); 66 | this.keys.push(key); 67 | }, context).then(() => { 68 | assert.deepEqual(context.values, [1, 2, 3]); 69 | assert.deepEqual(context.keys, ['a', 'b', 'c']); 70 | done(); 71 | }); 72 | }); 73 | 74 | it('stop iteration (array)', done => { 75 | const values = []; 76 | const keys = []; 77 | chillout.forEach([1, 2, 3], (value, i) => { 78 | values.push(value); 79 | keys.push(i); 80 | if (value === 2) { 81 | return chillout.StopIteration; 82 | } 83 | }).then(() => { 84 | assert.deepEqual(values, [1, 2]); 85 | assert.deepEqual(keys, [0, 1]); 86 | done(); 87 | }); 88 | }); 89 | 90 | it('stop nested Promise iteration (array)', done => { 91 | const values = []; 92 | const keys = []; 93 | chillout.forEach([1, 2, 3], (value, i) => { 94 | values.push(value); 95 | keys.push(i); 96 | if (value === 2) { 97 | return new Promise((resolve, reject) => { 98 | resolve(chillout.StopIteration); 99 | }); 100 | } 101 | }).then(() => { 102 | assert.deepEqual(values, [1, 2]); 103 | assert.deepEqual(keys, [0, 1]); 104 | done(); 105 | }); 106 | }); 107 | 108 | it('stop iteration (object)', done => { 109 | const values = []; 110 | const keys = []; 111 | chillout.forEach({ a: 1, b: 2, c: 3 }, (value, key) => { 112 | values.push(value); 113 | keys.push(key); 114 | if (value === 2) { 115 | return chillout.StopIteration; 116 | } 117 | }).then(() => { 118 | assert.deepEqual(values, [1, 2]); 119 | assert.deepEqual(keys, ['a', 'b']); 120 | done(); 121 | }); 122 | }); 123 | 124 | it('stop nested Promise iteration (object)', done => { 125 | const values = []; 126 | const keys = []; 127 | chillout.forEach({ a: 1, b: 2, c: 3 }, (value, key) => { 128 | values.push(value); 129 | keys.push(key); 130 | if (value === 2) { 131 | return new Promise((resolve, reject) => { 132 | resolve(chillout.StopIteration); 133 | }); 134 | } 135 | }).then(() => { 136 | assert.deepEqual(values, [1, 2]); 137 | assert.deepEqual(keys, ['a', 'b']); 138 | done(); 139 | }); 140 | }); 141 | 142 | it('throw an error (array)', done => { 143 | chillout.forEach([1], (value, i) => { 144 | throw [i, value]; 145 | }).then(() => { 146 | throw 'error'; 147 | }).catch(e => { 148 | assert.deepEqual(e, [0, 1]); 149 | done(); 150 | }); 151 | }); 152 | 153 | it('throw an error (object)', done => { 154 | chillout.forEach({ a: 1 }, (value, key) => { 155 | throw [key, value]; 156 | }).then(() => { 157 | throw 'error'; 158 | }).catch(e => { 159 | assert.deepEqual(e, ['a', 1]); 160 | done(); 161 | }); 162 | }); 163 | }); 164 | -------------------------------------------------------------------------------- /dist/chillout.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * chillout v5.0.0 - Reduce CPU usage by non-blocking async loop and psychologically speed up in JavaScript 3 | * Copyright (c) 2017-2021 polygon planet 4 | * https://github.com/polygonplanet/chillout 5 | * @license MIT 6 | */ 7 | !function(t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).chillout=t()}(function(){return function r(o,i,u){function f(n,t){if(!i[n]){if(!o[n]){var e="function"==typeof require&&require;if(!t&&e)return e(n,!0);if(c)return c(n,!0);throw(e=new Error("Cannot find module '"+n+"'")).code="MODULE_NOT_FOUND",e}e=i[n]={exports:{}},o[n][0].call(e.exports,function(t){return f(o[n][1][t]||t)},e,e.exports,r,o,i,u)}return i[n].exports}for(var c="function"==typeof require&&require,t=0;tt.length)&&(n=t.length);for(var e=0,r=new Array(n);e { 5 | const sleep = msec => new Promise(resolve => setTimeout(resolve, msec)); 6 | 7 | // Check whether the callback order does not change with async sleep 8 | const checkSleep = async () => { 9 | const startTime = Date.now(); 10 | await sleep(100); 11 | const elapsedTime = Date.now() - startTime; 12 | // Includes the time lag 13 | assert(elapsedTime >= 90); 14 | }; 15 | 16 | describe('forEach', () => { 17 | it('native forEach', done => { 18 | const test_native_forEach = async () => { 19 | const logs = []; 20 | logs.push('start'); 21 | 22 | // Native forEach does not work expect order in multiple asynchronous callbacks 23 | await [1, 2, 3].forEach(async value => { 24 | await checkSleep(); 25 | logs.push(value); 26 | }); 27 | 28 | logs.push('done'); 29 | return logs; 30 | }; 31 | 32 | (async () => { 33 | const logs = await test_native_forEach(); 34 | logs.push('done func'); 35 | assert.deepEqual(logs, ['start', 'done', 'done func']); 36 | done(); 37 | })(); 38 | }); 39 | 40 | it('chillout.forEach', done => { 41 | const test_chillout_forEach = async () => { 42 | const logs = []; 43 | logs.push('start'); 44 | 45 | await chillout.forEach([1, 2, 3], async value => { 46 | await checkSleep(); 47 | logs.push(value); 48 | }); 49 | logs.push('done'); 50 | return logs; 51 | }; 52 | 53 | (async () => { 54 | const logs = await test_chillout_forEach(); 55 | logs.push('done func'); 56 | assert.deepEqual(logs, ['start', 1, 2, 3, 'done', 'done func']); 57 | done(); 58 | })(); 59 | }); 60 | }); 61 | 62 | 63 | describe('repeat', () => { 64 | it('native for loop', done => { 65 | const test_native_forLoop = async () => { 66 | const logs = []; 67 | logs.push('start'); 68 | 69 | for (const i of [0, 1, 2]) { 70 | await checkSleep(); 71 | logs.push(i); 72 | } 73 | 74 | logs.push('done'); 75 | return logs; 76 | }; 77 | 78 | (async () => { 79 | const logs = await test_native_forLoop(); 80 | logs.push('done func'); 81 | assert.deepEqual(logs, ['start', 0, 1, 2, 'done', 'done func']); 82 | done(); 83 | })(); 84 | }); 85 | 86 | it('chillout.repeat', done => { 87 | const test_chillout_repeat = async () => { 88 | const logs = []; 89 | logs.push('start'); 90 | 91 | await chillout.repeat(3, async i => { 92 | await checkSleep(); 93 | logs.push(i); 94 | }); 95 | 96 | logs.push('done'); 97 | return logs; 98 | }; 99 | 100 | (async () => { 101 | const logs = await test_chillout_repeat(); 102 | logs.push('done func'); 103 | assert.deepEqual(logs, ['start', 0, 1, 2, 'done', 'done func']); 104 | done(); 105 | })(); 106 | }); 107 | }); 108 | 109 | 110 | describe('until', () => { 111 | it('native while', done => { 112 | const test_native_whileLoop = async () => { 113 | const logs = []; 114 | logs.push('start'); 115 | 116 | let i = 0; 117 | while (i !== 3) { 118 | await checkSleep(); 119 | logs.push(i); 120 | i++; 121 | } 122 | 123 | logs.push('done'); 124 | return logs; 125 | }; 126 | 127 | (async () => { 128 | const logs = await test_native_whileLoop(); 129 | logs.push('done func'); 130 | assert.deepEqual(logs, ['start', 0, 1, 2, 'done', 'done func']); 131 | done(); 132 | })(); 133 | }); 134 | 135 | it('chillout.until', done => { 136 | const test_chillout_until = async () => { 137 | const logs = []; 138 | logs.push('start'); 139 | 140 | let i = 0; 141 | await chillout.until(async () => { 142 | await checkSleep(); 143 | logs.push(i); 144 | i++; 145 | if (i === 3) { 146 | return chillout.StopIteration; 147 | } 148 | }); 149 | 150 | logs.push('done'); 151 | return logs; 152 | }; 153 | 154 | (async () => { 155 | const logs = await test_chillout_until(); 156 | logs.push('done func'); 157 | assert.deepEqual(logs, ['start', 0, 1, 2, 'done', 'done func']); 158 | done(); 159 | })(); 160 | }); 161 | }); 162 | 163 | 164 | describe('waitUntil', () => { 165 | it('native while', done => { 166 | const test_native_whileLoop = async () => { 167 | const logs = []; 168 | logs.push('start'); 169 | 170 | let i = 0; 171 | while (i !== 3) { 172 | await checkSleep(); 173 | logs.push(i); 174 | i++; 175 | } 176 | 177 | logs.push('done'); 178 | return logs; 179 | }; 180 | 181 | (async () => { 182 | const logs = await test_native_whileLoop(); 183 | logs.push('done func'); 184 | assert.deepEqual(logs, ['start', 0, 1, 2, 'done', 'done func']); 185 | done(); 186 | })(); 187 | }); 188 | 189 | it('chillout.waitUntil', done => { 190 | const test_chillout_waitUntil = async () => { 191 | const logs = []; 192 | logs.push('start'); 193 | 194 | let i = 0; 195 | await chillout.waitUntil(async () => { 196 | await checkSleep(); 197 | logs.push(i); 198 | i++; 199 | if (i === 3) { 200 | return chillout.StopIteration; 201 | } 202 | }); 203 | 204 | logs.push('done'); 205 | return logs; 206 | }; 207 | 208 | (async () => { 209 | const logs = await test_chillout_waitUntil(); 210 | logs.push('done func'); 211 | assert.deepEqual(logs, ['start', 0, 1, 2, 'done', 'done func']); 212 | done(); 213 | })(); 214 | }); 215 | }); 216 | 217 | describe('forOf', () => { 218 | it('native for-of loop', done => { 219 | const test_native_forOfLoop = async () => { 220 | const logs = []; 221 | logs.push('start'); 222 | 223 | for (const value of [1, 2, 3]) { 224 | await checkSleep(); 225 | logs.push(value); 226 | } 227 | 228 | logs.push('done'); 229 | return logs; 230 | }; 231 | 232 | (async () => { 233 | const logs = await test_native_forOfLoop(); 234 | logs.push('done func'); 235 | assert.deepEqual(logs, ['start', 1, 2, 3, 'done', 'done func']); 236 | done(); 237 | })(); 238 | }); 239 | 240 | it('chillout.forOf', done => { 241 | const test_chillout_forOf = async () => { 242 | const logs = []; 243 | logs.push('start'); 244 | 245 | await chillout.forOf([1, 2, 3], async value => { 246 | await checkSleep(); 247 | logs.push(value); 248 | }); 249 | 250 | logs.push('done'); 251 | return logs; 252 | }; 253 | 254 | (async () => { 255 | const logs = await test_chillout_forOf(); 256 | logs.push('done func'); 257 | assert.deepEqual(logs, ['start', 1, 2, 3, 'done', 'done func']); 258 | done(); 259 | })(); 260 | }); 261 | }); 262 | }); 263 | -------------------------------------------------------------------------------- /README-ja.md: -------------------------------------------------------------------------------- 1 | chillout.js 2 | =========== 3 | 4 | 重いループのCPU負荷を軽減し、体感的・心理的に高速化するJavaScriptライブラリ。 5 | 6 | [![NPM Version](https://img.shields.io/npm/v/chillout.svg)](https://www.npmjs.com/package/chillout) 7 | [![Build Status](https://travis-ci.com/polygonplanet/chillout.svg?branch=master)](https://travis-ci.com/polygonplanet/chillout) 8 | [![Bundle Size (minified)](https://img.shields.io/github/size/polygonplanet/chillout/dist/chillout.min.js.svg)](https://github.com/polygonplanet/chillout/blob/master/dist/chillout.min.js) 9 | [![GitHub License](https://img.shields.io/github/license/polygonplanet/chillout.svg)](https://github.com/polygonplanet/chillout/blob/master/LICENSE) 10 | 11 | ## Table of contents 12 | 13 | * [概要](#概要) 14 | * [インストール](#インストール) 15 | * [動作環境](#動作環境) 16 | * [async / await](#async--await) 17 | * [ベンチマーク](#ベンチマーク) 18 | * [API](#api) 19 | + [forEach](#foreach) 20 | + [repeat](#repeat) 21 | + [until](#until) 22 | + [waitUntil](#waituntil) 23 | + [forOf](#forof) 24 | * [比較表](#比較表) 25 | * [Pull Request](#pull-request) 26 | * [ライセンス](#ライセンス) 27 | 28 | ## 概要 29 | 30 | chillout.js は「処理時間を短くする」という物理的な高速化とは違い、CPU負荷を軽減しリソースに余裕を持たせ、重い処理でも軽く感じさせることでユーザーにとって体感的・心理的な高速化につなげる JavaScript ライブラリです。 31 | 32 | ### 重い処理から開放されるために 33 | 34 | 重いWebページやゲームでは画面が読み込み中のままカクカクだったり、Webページに限らずnode.jsでバッチ処理するときでも高CPU負荷が続くとマシンごと重くなってしまいます。 35 | 36 | 特にスペックの低い端末では、CPU使用率100%の状態が続くと加熱されて冷却ファンが激しく回り、そのまま使い続けると冷却が追いつかずに熱暴走してしまう可能性もあります。 37 | 38 | ### JavaScriptでCPU負荷を軽減するには? 39 | 40 | 重い処理のほとんどはループ処理によって発生します。ループの中でさらにループ、その中でさらにループ…。単純に考えた場合、そうならないようループの途中で一定時間処理を休止させればいいんですが、それができません。 41 | JavaScript には一定の時間休む sleep のような機能がないからです。そこで、sleepするにはどうするか?というと「非同期」でループ処理します。 42 | 43 | JavaScript では、`setTimeout` や `process.nextTick` を使って同期処理を非同期化できます。 44 | それと `Promise` を組み合わせると sleep のように一定時間CPUを休ませる非同期処理が実現できます。 45 | 46 | ### 処理時間を短くするんじゃなく、体感速度を向上させる体感的・心理的な高速化 47 | 48 | 処理の高速化というと、とにかく1ミリ秒でも処理時間を短くすることが手法とされますが、Webページやアプリ、ゲームといった、人が画面を見たり操作する場合 心理的に「速い」と感じればユーザーのストレスが減り結果として高速化につながります。 49 | 50 | chillout.js は、ループ処理が重いときにはCPUが休まるくらいの休止時間、処理が速いときには休止時間なしか、わずかな休止時間をいれ本来のループを邪魔しないようにし、結果としてカクカクするような重さを感じさせずにループを実行します。 51 | 52 | ほとんどの場合、既存の JavaScript ループの代わりに chillout.js のループを使うことで CPU 使用率を減らすことができます。非同期ループとして扱う必要がありますが `async/await` を使うと扱いやすくなります。 53 | 54 | また、処理が重くなるとでる **「警告: 応答のないスクリプト」** といったブラウザ警告なしでJavaScriptを実行できます。 55 | 56 | 57 | ブラウザ上、Electron、Node.js などの環境で使えます。 58 | 59 | 60 | ## インストール 61 | 62 | ### npm 63 | 64 | ```bash 65 | $ npm install chillout 66 | ``` 67 | 68 | ### CDN 69 | 70 | [jsdelivr.com](https://www.jsdelivr.com/package/npm/chillout) または [cdnjs.com](https://cdnjs.com/libraries/chillout) で CDN が利用できます。 71 | 72 | ### 使い方 73 | 74 | `require` で使う場合の例: 75 | 76 | ```javascript 77 | var chillout = require('chillout'); 78 | 79 | chillout.forEach([1, 2, 3], function(value) { 80 | console.log(value); 81 | }).then(function() { 82 | console.log('done'); 83 | }); 84 | 85 | // 1 86 | // 2 87 | // 3 88 | // 'done' 89 | ``` 90 | 91 | ブラウザで実行してる場合は、**chillout** というオブジェクトがグローバル ( `window.chillout` ) に定義されます。 92 | 93 | ## 動作環境 94 | 95 | `Promise` が動けば使えます(最新のブラウザはどれも動きます)。 96 | 97 | 98 | (古い環境で `Promise` がサポートされてない環境の場合は、[es6-shim](https://github.com/paulmillr/es6-shim) や、他の `Promise` polyfill を使ってください。) 99 | 100 | ## async / await 101 | 102 | `async/await` が使える環境ならより簡潔に書けます。 chillout.js のAPIはすべて Promise を返すため `async/await` で扱えるようになっています。 103 | 104 | 105 | ## ベンチマーク 106 | 107 | forループと `chillout.repeat` を比較します。 108 | 109 | ```javascript 110 | function heavyProcess() { 111 | var v; 112 | for (var i = 0; i < 5000; i++) { 113 | for (var j = 0; j < 5000; j++) { 114 | v = i * j; 115 | } 116 | } 117 | return v; 118 | } 119 | ``` 120 | 121 | ### forループ 122 | 123 | ```javascript 124 | var time = Date.now(); 125 | for (var i = 0; i < 1000; i++) { 126 | heavyProcess(); 127 | } 128 | var processingTime = Date.now() - time; 129 | console.log(processingTime); 130 | ``` 131 | 132 | ![CPU usage without chillout](https://raw.github.com/wiki/polygonplanet/chillout/images/benchmark-cpu-usage-without-chillout.png) 133 | 134 | * 処理時間: 107510ms. 135 | * CPU平均使用率(Nodeプロセス): **97.13%** 136 | 137 | ### chillout.repeat 138 | 139 | 140 | ```javascript 141 | var time = Date.now(); 142 | chillout.repeat(1000, function(i) { 143 | heavyProcess(); 144 | }).then(function() { 145 | var processingTime = Date.now() - time; 146 | console.log(processingTime); 147 | }); 148 | ``` 149 | 150 | ![CPU usage with chillout](https://raw.github.com/wiki/polygonplanet/chillout/images/benchmark-cpu-usage-using-chillout.png) 151 | 152 | * 処理時間: 138432ms. 153 | * CPU平均使用率(Nodeプロセス): **73.88%** 154 | 155 | ### ベンチマーク結果 156 | 157 | ![CPU usage with chillout](https://raw.github.com/wiki/polygonplanet/chillout/images/benchmark-cpu-usage-compare.png) 158 | 159 | |   | ForStatement (for文) | chillout.repeat | 160 | | ------------------------------- | --------------------:| ---------------:| 161 | | 処理時間 | 107510ms. | 138432ms. | 162 | | CPU平均使用率(Nodeプロセス) | **97.13%** | **73.88%** | 163 | 164 | 165 | `chillout.repeat` は forループ よりも低いCPU使用率で実行されているのが確認できます。 166 | chillout.js は、より低いCPU使用率と自然な速さでJavaScriptを実行できますが、処理速度は少し遅くなります。 167 | 168 | JavaScriptのパフォーマンスにおいて最も重要なことの一つは、数値的な速度ではなく安定したレスポンスによってユーザーにストレスを与えずに実行することです。これはブラウザ上で実行される場合、体感的な高速化の手段として特に重要です。 169 | 170 | *(ベンチマーク: chillout v3.1.2, Windows8.1 / Intel(R) Atom(TM) CPU Z3740 1.33GHz)* 171 | 172 | ### ベンチマークを実行 173 | 174 | `npm run benchmark` でベンチマークを実行できます。 175 | 176 | ---- 177 | 178 | ## API 179 | 180 | * [forEach](#foreach) 181 | * [repeat](#repeat) 182 | * [until](#until) 183 | * [waitUntil](#waituntil) 184 | * [forOf](#forof) 185 | 186 | ### forEach 187 | 188 | 与えられた関数 `callback` を、配列またはオブジェクトの各要素に対して一度ずつ実行します。 189 | 関数内で `chillout.StopIteration` を返すか、エラーが発生すると、それ以降のループ処理は実行されません。 190 | このメソッドは JavaScript の `Array forEach` のように使えます。 191 | 192 | * chillout.**forEach** ( obj, callback [, context ] ) 193 | * @param {_array|object_} _obj_ 対象の配列またはオブジェクト。 194 | * @param {_function_} *callback* 各要素に対して実行するコールバック関数で、3つの引数をとります。 195 | - value: 現在処理されている配列の要素、またはオブジェクトの値。 196 | - key: 現在処理されている配列の要素のインデックス、またはオブジェクトのキー。 197 | - obj: `forEach` が適用されている配列またはオブジェクト。 198 | * @param {_object_} [_context_] 任意。コールバック内で `this` として使用する値。 199 | * @return {_promise_} Promiseオブジェクトを返します。 200 | 201 | 配列のループ例: 202 | 203 | ```javascript 204 | var values = ['a', 'b', 'c']; 205 | 206 | chillout.forEach(values, function(value, key, obj) { 207 | console.log(value); 208 | }).then(function() { 209 | console.log('done'); 210 | }); 211 | 212 | // 'a' 213 | // 'b' 214 | // 'c' 215 | // 'done' 216 | ``` 217 | 218 | オブジェクトのループ例: 219 | 220 | ```javascript 221 | var values = { 222 | a: 1, 223 | b: 2, 224 | c: 3 225 | }; 226 | 227 | chillout.forEach(values, function(value, key, obj) { 228 | console.log(key + ':' + value); 229 | }).then(function() { 230 | console.log('done'); 231 | }); 232 | 233 | // 'a:1' 234 | // 'b:2' 235 | // 'c:3' 236 | // 'done' 237 | ``` 238 | 239 | `async / await` を使ってループする例: 240 | 241 | この例はファイルの内容をすべて出力し、最後に 'done' を出力します。 242 | 243 | ```javascript 244 | async function getFileContents(url) { 245 | const response = await fetch(url); 246 | return response.text(); 247 | } 248 | 249 | // chillout.forEachのコールバックでasync functionを渡す 250 | async function logFiles() { 251 | const files = ['/file1.txt', '/file2.txt', '/file3.txt']; 252 | await chillout.forEach(files, async url => { 253 | const contents = await getFileContents(url); 254 | console.log(contents); 255 | }); 256 | console.log('done'); 257 | } 258 | 259 | logFiles(); 260 | ``` 261 | 262 | ### repeat 263 | 264 | 与えられた関数 `callback` をを、引数で与えられた数だけ実行します。 265 | 関数内で `chillout.StopIteration` を返すか、エラーが発生すると、それ以降のループ処理は実行されません。 266 | このメソッドは JavaScript の `for` ステートメントのように使えます。 267 | 268 | * chillout.**repeat** ( count, callback [, context ] ) 269 | * @param {_number|object_} _count_ 繰り返す回数またはオブジェクトで指定。 270 | オブジェクトで指定する場合は以下のキーが有効です。 271 | - start: 開始する数。 272 | - step: ステップ数。 273 | - done: 終了する数。 274 | * @param {_function_} _callback_ 各ループに対して実行するコールバック関数で、1つの引数をとります。 275 | - i: 現在の数。 276 | * @param {_object_} [_context_] 任意。コールバック内で `this` として使用する値。 277 | * @return {_promise_} Promiseオブジェクトを返します。 278 | 279 | 回数を指定する例: 280 | 281 | ```javascript 282 | chillout.repeat(5, function(i) { 283 | console.log(i); 284 | }).then(function() { 285 | console.log('done'); 286 | }); 287 | 288 | // 0 289 | // 1 290 | // 2 291 | // 3 292 | // 4 293 | // 'done' 294 | ``` 295 | 296 | オブジェクトで指定する例: 297 | 298 | ```javascript 299 | chillout.repeat({ start: 10, step: 2, done: 20 }, function(i) { 300 | console.log(i); 301 | }).then(function() { 302 | console.log('done'); 303 | }); 304 | 305 | // 10 306 | // 12 307 | // 14 308 | // 16 309 | // 18 310 | // 'done' 311 | ``` 312 | 313 | `async / await` を使ってループする例: 314 | 315 | この例は `/api/users/0` から `api/users/9` までのユーザーデータを出力し、最後に 'done' を出力します。 316 | 317 | 318 | ```javascript 319 | async function getUser(userId) { 320 | const response = await fetch(`/api/users/${userId}`); 321 | return response.json(); 322 | } 323 | 324 | // chillout.repeatのコールバックでasync functionを渡す 325 | async function logUsers() { 326 | await chillout.repeat(10, async i => { 327 | const user = await getUser(i); 328 | console.log(user); 329 | }); 330 | console.log('done'); 331 | } 332 | 333 | logUsers(); 334 | ``` 335 | 336 | 337 | ### until 338 | 339 | 与えられた関数 `callback` を、 `chillout.StopIteration` が返されるかエラーが発生するまで繰り返します。 340 | このメソッドは JavaScript の `while (true) { ... }` ステートメントのように使えます。 341 | 342 | * chillout.**until** ( callback [, context ] ) 343 | * @param {_function_} _callback_ 各ループに対して実行するコールバック関数。 344 | * @param {_object_} [_context_] 任意。コールバック内で `this` として使用する値。 345 | * @return {_promise_} Promiseオブジェクトを返します。 346 | 347 | ```javascript 348 | var i = 0; 349 | chillout.until(function() { 350 | console.log(i); 351 | i++; 352 | if (i === 5) { 353 | return chillout.StopIteration; // ループを止める 354 | } 355 | }).then(function() { 356 | console.log('done'); 357 | }); 358 | 359 | // 0 360 | // 1 361 | // 2 362 | // 3 363 | // 4 364 | // 'done' 365 | ``` 366 | 367 | `async / await` を使ってループする例: 368 | 369 | この例はファイルの変更を監視して、変更された内容を出力します。 370 | 371 | ```javascript 372 | // ミリ秒(msec)が過ぎるまで待機する関数 373 | function sleep(msec) { 374 | return new Promise(resolve => setTimeout(resolve, msec)); 375 | } 376 | 377 | async function getFileContents(url) { 378 | const response = await fetch(url); 379 | return response.text(); 380 | } 381 | 382 | let previous = null; 383 | 384 | // chillout.untilのコールバックでasync functionを渡す 385 | async function logNewFileContents() { 386 | await chillout.until(async () => { 387 | const contents = await getFileContents('./file1.txt'); 388 | if (previous === null) { 389 | previous = contents; 390 | } 391 | 392 | if (contents !== previous) { 393 | console.log('file changed!'); 394 | previous = contents; 395 | return chillout.StopIteration; // ループを止める 396 | } 397 | await sleep(1000); 398 | }); 399 | console.log(previous); 400 | } 401 | 402 | logNewFileContents(); 403 | ``` 404 | 405 | ### waitUntil 406 | 407 | 与えられた関数 `callback` を、 `chillout.StopIteration` が返されるかエラーが発生するまで繰り返します。 408 | このメソッドは JavaScript の `while (true) { ... }` ステートメントのように使え、 [`until`](#until) と同じ動作をしますが、CPU に負荷がかからないよう `until` よりゆっくり実行します。 409 | このメソッドは、何らかの処理が終わるまで待ちたいときに向いています。 410 | 411 | * chillout.**waitUntil** ( callback [, context ] ) 412 | * @param {_function_} _callback_ 各ループに対して実行するコールバック関数。 413 | * @param {_object_} [_context_] 任意。コールバック内で `this` として使用する値。 414 | * @return {_promise_} Promiseオブジェクトを返します。 415 | 416 | ```javascript 417 | chillout.waitUntil(function() { 418 | // body要素が読み込まれるまで待機する 419 | if (document.body) { 420 | return chillout.StopIteration; // ループを止める 421 | } 422 | }).then(function() { 423 | document.body.innerHTML += 'body loaded'; 424 | }); 425 | ``` 426 | 427 | 何らかの処理が終わるまで待つ例: 428 | 429 | ```javascript 430 | someProcessing(); 431 | chillout.waitUntil(function() { 432 | if (isSomeProcessingDone) { 433 | return chillout.StopIteration; // break loop 434 | } 435 | }).then(function() { 436 | nextProcessing(); 437 | }); 438 | ``` 439 | 440 | ### forOf 441 | 442 | 列挙可能なプロパティに対して、ループ処理を行います。 443 | これは `for-of` ステートメントと同じループ処理をします。 444 | 445 | 与えられた関数 `callback` を各ループに対して実行します。 446 | 関数内で `chillout.StopIteration` を返すか、エラーが発生すると、それ以降のループは実行されません。 447 | 448 | * chillout.**forOf** ( iterable, callback [, context ] ) 449 | * @param {_array|string|object_} _iterable_ 列挙可能なプロパティに対して、ループ処理を行うオブジェクト。 450 | * @param {_function_} _callback_ 各ループに対して実行するコールバック関数で、1つの引数をとります。 451 | - value: 各ループ処理におけるプロパティの値。 452 | * @param {_object_} [_context_] 任意。コールバック内で `this` として使用する値。 453 | * @return {_promise_} Promiseオブジェクトを返します。 454 | 455 | 配列のループ例: 456 | 457 | ```javascript 458 | chillout.forOf([1, 2, 3], function(value) { 459 | console.log(value); 460 | }).then(function() { 461 | console.log('done'); 462 | }); 463 | 464 | // 1 465 | // 2 466 | // 3 467 | // 'done' 468 | ``` 469 | 470 | 文字列のループ例: 471 | 472 | ```javascript 473 | chillout.forOf('abc', function(value) { 474 | console.log(value); 475 | }).then(function() { 476 | console.log('done'); 477 | }); 478 | 479 | // 'a' 480 | // 'b' 481 | // 'c' 482 | // 'done' 483 | ``` 484 | 485 | ## 比較表 486 | 487 | 既存のJavaScriptループを chillout.js のAPIに置き換えるとCPU負荷を軽減することができます。 488 | 489 | 変換例: 490 | 491 | | JavaScript Statement | chillout | 492 | | -------------------------------------|-------------------------------------------------------------------------------| 493 | | [1, 2, 3].forEach(function(v, i) {}) | chillout.forEach([1, 2, 3], function(v, i) {}) | 494 | | for (i = 0; i < 5; i++) {} | chillout.repeat(5, function(i) {}) | 495 | | for (i = 10; i < 20; i += 2) {} | chillout.repeat({ start: 10, step: 2, done: 20 }, function(i) {}) | 496 | | while (true) {} | chillout.until(function() {}) | 497 | | while (cond()) {
  doSomething();
} | chillout.until(function() {
  if (!cond()) return chillout.StopIteration;
  doSomething();
}) | 498 | | for (value of [1, 2, 3]) {} | chillout.forOf([1, 2, 3], function(value) {}) | 499 | 500 | ※ `async/await` が使える環境ではより簡潔に書けます。 chillout.js のAPIはすべて Promise を返すので `async/await` で扱えるようになっています。 501 | 502 | ## Pull Request 503 | 504 | バグレポートや機能要望などの Issues や、Pull Request を歓迎しています。 505 | Pull Requestの際は、 `npm run test` を実行してエラーがないことを確認していただけると助かります。 506 | 507 | ## ライセンス 508 | 509 | MIT 510 | -------------------------------------------------------------------------------- /dist/chillout.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * chillout v5.0.0 - Reduce CPU usage by non-blocking async loop and psychologically speed up in JavaScript 3 | * Copyright (c) 2017-2021 polygon planet 4 | * https://github.com/polygonplanet/chillout 5 | * @license MIT 6 | */ 7 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.chillout = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } 137 | 138 | function _iterableToArrayLimit(arr, i) { if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } 139 | 140 | function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } 141 | 142 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 143 | 144 | var _require = require('./util'), 145 | isThenable = _require.isThenable; 146 | 147 | var StopIteration = require('./stop-iteration'); 148 | 149 | var nextTick = require('./next-tick'); 150 | 151 | var MAX_DELAY = 1500; 152 | 153 | module.exports = function iterate(it) { 154 | var interval = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; 155 | return new Promise(function (resolve, reject) { 156 | var totalTime = 0; 157 | 158 | function doIterate() { 159 | var cycleStartTime = Date.now(); 160 | var cycleEndTime; 161 | 162 | try { 163 | var _loop = function _loop() { 164 | var _it$next = it.next(), 165 | _it$next2 = _slicedToArray(_it$next, 2), 166 | isStop = _it$next2[0], 167 | value = _it$next2[1]; 168 | 169 | if (isThenable(value)) { 170 | value.then(function (awaitedValue) { 171 | if (isStop) { 172 | resolve(awaitedValue); 173 | } else if (awaitedValue === StopIteration) { 174 | resolve(); 175 | } else { 176 | doIterate(); 177 | } 178 | }, reject); 179 | return { 180 | v: void 0 181 | }; 182 | } 183 | 184 | if (isStop) { 185 | resolve(value); 186 | return { 187 | v: void 0 188 | }; 189 | } 190 | 191 | if (value === StopIteration) { 192 | resolve(); 193 | return { 194 | v: void 0 195 | }; 196 | } 197 | 198 | if (interval > 0) { 199 | return "break"; 200 | } 201 | 202 | var endTime = Date.now(); 203 | cycleEndTime = endTime - cycleStartTime; 204 | totalTime += cycleEndTime; // Break the loop when the process is continued for more than 1s 205 | 206 | if (totalTime > 1000) { 207 | return "break"; 208 | } // Delay is not required for fast iteration 209 | 210 | 211 | if (cycleEndTime < 10) { 212 | return "continue"; 213 | } 214 | 215 | return "break"; 216 | }; 217 | 218 | for (;;) { 219 | var _ret = _loop(); 220 | 221 | if (_ret === "break") break; 222 | if (_ret === "continue") continue; 223 | if (_typeof(_ret) === "object") return _ret.v; 224 | } 225 | } catch (e) { 226 | reject(e); 227 | return; 228 | } 229 | 230 | if (interval > 0) { 231 | // Short timeouts will throttled to >=4ms by the browser, so we execute tasks 232 | // slowly enough to reduce CPU load 233 | var delay = Math.min(MAX_DELAY, Date.now() - cycleStartTime + interval); 234 | setTimeout(doIterate, delay); 235 | } else { 236 | // Add delay corresponding to the processing speed 237 | var _delay = Math.min(MAX_DELAY, cycleEndTime / 3); 238 | 239 | totalTime = 0; 240 | 241 | if (_delay > 10) { 242 | setTimeout(doIterate, _delay); 243 | } else { 244 | nextTick(doIterate); 245 | } 246 | } 247 | } // The first call doesn't need to wait, so it will execute a task immediately 248 | 249 | 250 | nextTick(doIterate); 251 | }); 252 | }; 253 | 254 | },{"./next-tick":5,"./stop-iteration":6,"./util":7}],4:[function(require,module,exports){ 255 | "use strict"; 256 | 257 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 258 | 259 | var _require = require('./util'), 260 | isArrayLike = _require.isArrayLike; 261 | 262 | exports.forEach = function (obj, callback, context) { 263 | var i = 0; 264 | var len; 265 | 266 | if (isArrayLike(obj)) { 267 | len = obj.length; 268 | return { 269 | next: function next() { 270 | if (i >= len) { 271 | return [true, null]; 272 | } 273 | 274 | var value = callback.call(context, obj[i], i, obj); 275 | i++; 276 | return [false, value]; 277 | } 278 | }; 279 | } 280 | 281 | var keys = Object.keys(obj); 282 | len = keys.length; 283 | return { 284 | next: function next() { 285 | if (i >= len) { 286 | return [true, null]; 287 | } 288 | 289 | var key = keys[i++]; 290 | var value = callback.call(context, obj[key], key, obj); 291 | return [false, value]; 292 | } 293 | }; 294 | }; 295 | 296 | exports.repeat = function (count, callback, context) { 297 | var i; 298 | var step; 299 | var done; 300 | 301 | if (count && _typeof(count) === 'object') { 302 | i = count.start || 0; 303 | step = count.step || 1; 304 | done = count.done; 305 | } else { 306 | i = 0; 307 | step = 1; 308 | done = count; 309 | } 310 | 311 | return { 312 | next: function next() { 313 | var value = callback.call(context, i); 314 | i += step; 315 | 316 | if (i >= done) { 317 | return [true, value]; 318 | } 319 | 320 | return [false, value]; 321 | } 322 | }; 323 | }; 324 | 325 | exports.until = function (callback, context) { 326 | return { 327 | next: function next() { 328 | var value = callback.call(context); 329 | return [false, value]; 330 | } 331 | }; 332 | }; 333 | 334 | exports.forOf = function (iterable, callback, context) { 335 | var it = iterable[Symbol.iterator](); 336 | return { 337 | next: function next() { 338 | var nextIterator = it.next(); 339 | 340 | if (nextIterator.done) { 341 | return [true, null]; 342 | } 343 | 344 | var value = callback.call(context, nextIterator.value, iterable); 345 | return [false, value]; 346 | } 347 | }; 348 | }; 349 | 350 | },{"./util":7}],5:[function(require,module,exports){ 351 | "use strict"; 352 | 353 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 354 | 355 | var nextTick = function () { 356 | if (typeof setImmediate === 'function') { 357 | return function (task) { 358 | setImmediate(task); 359 | }; 360 | } 361 | 362 | if ((typeof process === "undefined" ? "undefined" : _typeof(process)) === 'object' && typeof process.nextTick === 'function') { 363 | return function (task) { 364 | process.nextTick(task); 365 | }; 366 | } 367 | 368 | if (typeof MessageChannel === 'function') { 369 | // http://www.nonblocking.io/2011/06/windownexttick.html 370 | var channel = new MessageChannel(); 371 | var head = {}; 372 | var tail = head; 373 | 374 | channel.port1.onmessage = function () { 375 | head = head.next; 376 | var task = head.task; 377 | delete head.task; 378 | task(); 379 | }; 380 | 381 | return function (task) { 382 | tail = tail.next = { 383 | task: task 384 | }; 385 | channel.port2.postMessage(0); 386 | }; 387 | } 388 | 389 | return function (task) { 390 | setTimeout(task, 0); 391 | }; 392 | }(); 393 | 394 | module.exports = nextTick; 395 | 396 | },{}],6:[function(require,module,exports){ 397 | "use strict"; 398 | 399 | var StopIteration = {}; 400 | module.exports = StopIteration; 401 | 402 | },{}],7:[function(require,module,exports){ 403 | "use strict"; 404 | 405 | exports.isThenable = function isThenable(x) { 406 | return x != null && typeof x.then === 'function'; 407 | }; 408 | 409 | exports.isArrayLike = function isArrayLike(x) { 410 | return x != null && typeof x.length === 'number' && x.length >= 0; 411 | }; 412 | 413 | },{}]},{},[2])(2) 414 | }); 415 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | chillout.js 2 | =========== 3 | 4 | [**README (日本語)**](README-ja.md) 5 | 6 | Reduce CPU usage by non-blocking asynchronous loop and psychologically speed up to improve the user experience in JavaScript. 7 | 8 | [![NPM Version](https://img.shields.io/npm/v/chillout.svg)](https://www.npmjs.com/package/chillout) 9 | [![Build Status](https://travis-ci.com/polygonplanet/chillout.svg?branch=master)](https://travis-ci.com/polygonplanet/chillout) 10 | [![Bundle Size (minified)](https://img.shields.io/github/size/polygonplanet/chillout/dist/chillout.min.js.svg)](https://github.com/polygonplanet/chillout/blob/master/dist/chillout.min.js) 11 | [![GitHub License](https://img.shields.io/github/license/polygonplanet/chillout.svg)](https://github.com/polygonplanet/chillout/blob/master/LICENSE) 12 | 13 | ## Table of contents 14 | 15 | * [Overview](#overview) 16 | * [Installation](#installation) 17 | * [Compatibility](#compatibility) 18 | * [async / await](#async--await) 19 | * [Benchmarks](#benchmarks) 20 | * [API](#api) 21 | + [forEach](#foreach) 22 | + [repeat](#repeat) 23 | + [until](#until) 24 | + [waitUntil](#waituntil) 25 | + [forOf](#forof) 26 | * [Comparison Table](#comparison-table) 27 | * [Contributing](#contributing) 28 | * [License](#license) 29 | 30 | ## Overview 31 | 32 | Unlike general accelerate way of the "to shorten the processing time physically", chillout.js speed up the JavaScript loops psychologically by reduce CPU usage and release resources to improve user experience. 33 | 34 | ### In order to improve slow processing 35 | 36 | We feel stress when page load or processing is slow and the screen is lagging. 37 | Moreover, there is a risk that the machine will down due to overheating because it can't keep up CPU cooling when continues running the heavy-load application with high CPU utilization rate. 38 | 39 | ### How to reduce CPU load in JavaScript? 40 | 41 | Most of the slow processing is caused by looping that have deep nested looping. 42 | If we think simply, it should wait for a little time like "sleep" in the looping, but we can't wait, because JavaScript have not "sleep" function. Then how to sleep in JavaScript? We can sleep by using `setTimeout` or `process.nextTick` with `Promise` in "asynchronous" processing. 43 | 44 | ### Speed up the JavaScript loops psychologically not physically 45 | 46 | We can feel like "fast" or "interesting" psychologically by relieving user stress, even if the processing is slow if your application or game will used by humans not AI. 47 | 48 | ### About chillout.js 49 | 50 | Provides asynchronous iteration functions that have a `Promise` based interface and it can run with low CPU usage. 51 | In most cases, you can reduce the CPU usage by using the non-blocking loop of chillout.js API instead native JavaScript loops in your code, but you need to change it to asynchronous loops. 52 | 53 | Chillout.js adds a little delay on each iteration if the processing is slow to maintain the CPU stability, and it continues iterate without delay if processing is fast. 54 | Therefore, it will realize friendly processing for your machine. 55 | And, it can execute JavaScript without **"Warning: Unresponsive Script"** alert in the browser. 56 | 57 | Chillout.js is a standalone library, and you can use it in the most of JavaScript environments (Browser, Electron, Node.js etc.). 58 | 59 | ## Installation 60 | 61 | ### npm 62 | 63 | ```bash 64 | $ npm install chillout 65 | ``` 66 | 67 | ### CDN 68 | 69 | chillout.js is available on [jsdelivr.com](https://www.jsdelivr.com/package/npm/chillout) and [cdnjs.com](https://cdnjs.com/libraries/chillout). 70 | 71 | ### Usage 72 | 73 | ```javascript 74 | var chillout = require('chillout'); 75 | 76 | chillout.forEach([1, 2, 3], function(value) { 77 | console.log(value); 78 | }).then(function() { 79 | console.log('done'); 80 | }); 81 | 82 | // 1 83 | // 2 84 | // 3 85 | // 'done' 86 | ``` 87 | 88 | Object **chillout** is defined in the global scope if running in the browser window. ( `window.chillout` ) 89 | 90 | ## Compatibility 91 | 92 | The limiting factor for browser/node support is the use of `Promise`. 93 | You can use [es6-shim](https://github.com/paulmillr/es6-shim) or other `Promise` polyfills. 94 | 95 | ## async / await 96 | 97 | You can write more simply using by `async/await` syntax. 98 | 99 | Because all APIs in chillout.js return Promise, it is easy to handle with `async/await`. 100 | 101 | ## Benchmarks 102 | 103 | Benchmarks the **ForStatement** and `chillout.repeat`. 104 | 105 | ```javascript 106 | function heavyProcess() { 107 | var v; 108 | for (var i = 0; i < 5000; i++) { 109 | for (var j = 0; j < 5000; j++) { 110 | v = i * j; 111 | } 112 | } 113 | return v; 114 | } 115 | ``` 116 | 117 | ### ForStatement 118 | 119 | ```javascript 120 | var time = Date.now(); 121 | for (var i = 0; i < 1000; i++) { 122 | heavyProcess(); 123 | } 124 | var processingTime = Date.now() - time; 125 | console.log(processingTime); 126 | ``` 127 | 128 | ![CPU usage without chillout](https://raw.github.com/wiki/polygonplanet/chillout/images/benchmark-cpu-usage-without-chillout.png) 129 | 130 | * Processing time: 107510ms. 131 | * CPU usage on Node process (Average): **97.13%** 132 | 133 | ### chillout.repeat 134 | 135 | 136 | ```javascript 137 | var time = Date.now(); 138 | chillout.repeat(1000, function(i) { 139 | heavyProcess(); 140 | }).then(function() { 141 | var processingTime = Date.now() - time; 142 | console.log(processingTime); 143 | }); 144 | ``` 145 | 146 | ![CPU usage with chillout](https://raw.github.com/wiki/polygonplanet/chillout/images/benchmark-cpu-usage-using-chillout.png) 147 | 148 | * Processing time: 138432ms. 149 | * CPU usage on Node process (Average): **73.88%** 150 | 151 | 152 | ### Benchmark Result 153 | 154 | ![CPU usage with chillout](https://raw.github.com/wiki/polygonplanet/chillout/images/benchmark-cpu-usage-compare.png) 155 | 156 | |   | ForStatement | chillout.repeat | 157 | | ------------------------------------ | ------------:| ---------------:| 158 | | Processing time | 107510ms. | 138432ms. | 159 | | CPU usage on Node process (Average) | **97.13%** | **73.88%** | 160 | 161 | 162 | You can confirm that `chillout.repeat` is running on a more low CPU usage than **ForStatement**. 163 | 164 | chillout.js can run JavaScript in a natural speed with low CPU usage, but processing speed will be a bit slow. 165 | 166 | One of the most important thing of performance in JavaScript, that is not numeric speed, but is to execute without causing stress to the user experience. 167 | 168 | 169 | *(Benchmarks: chillout v3.1.2, Windows8.1 / Intel(R) Atom(TM) CPU Z3740 1.33GHz)* 170 | 171 | ### Run Benchmark 172 | 173 | You can test benchmark with `npm run benchmark`. 174 | 175 | ---- 176 | 177 | ## API 178 | 179 | * [forEach](#foreach) 180 | * [repeat](#repeat) 181 | * [until](#until) 182 | * [waitUntil](#waituntil) 183 | * [forOf](#forof) 184 | 185 | ### forEach 186 | 187 | Executes a provided function once per array or object element. 188 | The iteration will break if the callback function returns `chillout.StopIteration`, or an error occurs. 189 | This method can be called like JavaScript `Array forEach`. 190 | 191 | * chillout.**forEach** ( obj, callback [, context ] ) 192 | * @param {_array|object_} _obj_ Target array or object 193 | * @param {_function_} *callback* Function to execute for each element, taking three arguments: 194 | - value: The current element being processed in the array/object 195 | - key: The key of the current element being processed in the array/object 196 | - obj: The array/object that `forEach` is being applied to 197 | * @param {_object_} [_context_] Value to use as `this` when executing callback 198 | * @return {_promise_} Return new Promise 199 | 200 | Example of array iteration: 201 | 202 | ```javascript 203 | var values = ['a', 'b', 'c']; 204 | 205 | chillout.forEach(values, function(value, key, obj) { 206 | console.log(value); 207 | }).then(function() { 208 | console.log('done'); 209 | }); 210 | 211 | // 'a' 212 | // 'b' 213 | // 'c' 214 | // 'done' 215 | ``` 216 | 217 | 218 | Example of object iteration: 219 | 220 | ```javascript 221 | var values = { 222 | a: 1, 223 | b: 2, 224 | c: 3 225 | }; 226 | 227 | chillout.forEach(values, function(value, key, obj) { 228 | console.log(key + ':' + value); 229 | }).then(function() { 230 | console.log('done'); 231 | }); 232 | 233 | // 'a:1' 234 | // 'b:2' 235 | // 'c:3' 236 | // 'done' 237 | ``` 238 | 239 | Example iteration using `async / await`: 240 | 241 | Output all file contents, and finally output 'done'. 242 | 243 | 244 | ```javascript 245 | async function getFileContents(url) { 246 | const response = await fetch(url); 247 | return response.text(); 248 | } 249 | 250 | // Passing an async function as a callback in chillout.forEach 251 | async function logFiles() { 252 | const files = ['/file1.txt', '/file2.txt', '/file3.txt']; 253 | await chillout.forEach(files, async url => { 254 | const contents = await getFileContents(url); 255 | console.log(contents); 256 | }); 257 | console.log('done'); 258 | } 259 | 260 | logFiles(); 261 | ``` 262 | 263 | 264 | ### repeat 265 | 266 | Executes a provided function the specified number times. 267 | The iteration will break if the callback function returns `chillout.StopIteration`, or an error occurs. 268 | This method can be called like JavaScript `for` statement. 269 | 270 | * chillout.**repeat** ( count, callback [, context ] ) 271 | * @param {_number|object_} _count_ The number of times or object for execute the function 272 | Following parameters are available if specify object: 273 | - start: The number of start 274 | - step: The number of step 275 | - done: The number of done 276 | * @param {_function_} _callback_ Function to execute for each times, taking one argument: 277 | - i: The current number 278 | * @param {_object_} [_context_] Value to use as `this` when executing callback 279 | * @return {_promise_} Return new Promise 280 | 281 | Example of specify number: 282 | 283 | ```javascript 284 | chillout.repeat(5, function(i) { 285 | console.log(i); 286 | }).then(function() { 287 | console.log('done'); 288 | }); 289 | 290 | // 0 291 | // 1 292 | // 2 293 | // 3 294 | // 4 295 | // 'done' 296 | ``` 297 | 298 | Example of specify object: 299 | 300 | ```javascript 301 | chillout.repeat({ start: 10, step: 2, done: 20 }, function(i) { 302 | console.log(i); 303 | }).then(function() { 304 | console.log('done'); 305 | }); 306 | 307 | // 10 308 | // 12 309 | // 14 310 | // 16 311 | // 18 312 | // 'done' 313 | ``` 314 | 315 | Example iteration using `async / await`: 316 | 317 | Output the user data from `/api/users/0` to `api/users/9`, and finally output 'done'. 318 | 319 | ```javascript 320 | async function getUser(userId) { 321 | const response = await fetch(`/api/users/${userId}`); 322 | return response.json(); 323 | } 324 | 325 | // Passing an async function as a callback in chillout.repeat 326 | async function logUsers() { 327 | await chillout.repeat(10, async i => { 328 | const user = await getUser(i); 329 | console.log(user); 330 | }); 331 | console.log('done'); 332 | } 333 | 334 | logUsers(); 335 | ``` 336 | 337 | 338 | ### until 339 | 340 | Executes a provided function until the `callback` returns `chillout.StopIteration`, or an error occurs. 341 | This method can be called like JavaScript `while (true) { ... }` statement. 342 | 343 | * chillout.**until** ( callback [, context ] ) 344 | * @param {_function_} _callback_ The function that is executed for each iteration 345 | * @param {_object_} [_context_] Value to use as `this` when executing callback 346 | * @return {_promise_} Return new Promise 347 | 348 | ```javascript 349 | var i = 0; 350 | chillout.until(function() { 351 | console.log(i); 352 | i++; 353 | if (i === 5) { 354 | return chillout.StopIteration; // break loop 355 | } 356 | }).then(function() { 357 | console.log('done'); 358 | }); 359 | 360 | // 0 361 | // 1 362 | // 2 363 | // 3 364 | // 4 365 | // 'done' 366 | ``` 367 | 368 | Example iteration using `async / await`: 369 | 370 | Watch for files changes, and finally output file changed contents. 371 | 372 | 373 | ```javascript 374 | // Sleep until msec 375 | function sleep(msec) { 376 | return new Promise(resolve => setTimeout(resolve, msec)); 377 | } 378 | 379 | async function getFileContents(url) { 380 | const response = await fetch(url); 381 | return response.text(); 382 | } 383 | 384 | // Passing an async function as a callback in chillout.until 385 | async function logNewFileContents() { 386 | let previous = null; 387 | let contents = null; 388 | await chillout.until(async () => { 389 | contents = await getFileContents('./file1.txt'); 390 | if (previous === null) { 391 | previous = contents; 392 | } 393 | if (contents !== previous) { 394 | console.log('file changed!'); 395 | return chillout.StopIteration; // break loop 396 | } 397 | await sleep(1000); 398 | }); 399 | console.log(contents); 400 | previous = contents; 401 | } 402 | 403 | logNewFileContents(); 404 | ``` 405 | 406 | ### waitUntil 407 | 408 | Executes a provided function until the `callback` returns `chillout.StopIteration`, or an error occurs. 409 | This method can be called like JavaScript `while (true) { ... }` statement, and it works same as [`until`](#until), but it executes tasks with more slowly interval than `until` to reduce CPU load. 410 | This method is useful when you want to wait until some processing done. 411 | 412 | * chillout.**waitUntil** ( callback [, context ] ) 413 | * @param {_function_} _callback_ The function that is executed for each iteration 414 | * @param {_object_} [_context_] Value to use as `this` when executing callback 415 | * @return {_promise_} Return new Promise 416 | 417 | ```javascript 418 | chillout.waitUntil(function() { 419 | // Wait until the DOM body element is loaded 420 | if (document.body) { 421 | return chillout.StopIteration; // break loop 422 | } 423 | }).then(function() { 424 | document.body.innerHTML += 'body loaded'; 425 | }); 426 | ``` 427 | 428 | 429 | Example to wait until some processing is done. 430 | 431 | 432 | ```javascript 433 | someProcessing(); 434 | chillout.waitUntil(function() { 435 | if (isSomeProcessingDone) { 436 | return chillout.StopIteration; // break loop 437 | } 438 | }).then(function() { 439 | nextProcessing(); 440 | }); 441 | ``` 442 | 443 | ### forOf 444 | 445 | Iterates the iterable objects, similar to the `for-of` statement. 446 | Executes a provided function once per element. 447 | The iteration will break if the callback function returns `chillout.StopIteration`, or an error occurs. 448 | 449 | * chillout.**forOf** ( iterable, callback [, context ] ) 450 | * @param {_array|string|object_} _iterable_ Target iterable objects 451 | * @param {_function_} _callback_ Function to execute for each element, taking one argument: 452 | - value: A value of a property on each iteration 453 | * @param {_object_} [_context_] Value to use as `this` when executing callback 454 | * @return {_promise_} Return new Promise 455 | 456 | Example of iterate array: 457 | 458 | ```javascript 459 | chillout.forOf([1, 2, 3], function(value) { 460 | console.log(value); 461 | }).then(function() { 462 | console.log('done'); 463 | }); 464 | 465 | // 1 466 | // 2 467 | // 3 468 | // 'done' 469 | ``` 470 | 471 | Example of iterate string: 472 | 473 | ```javascript 474 | chillout.forOf('abc', function(value) { 475 | console.log(value); 476 | }).then(function() { 477 | console.log('done'); 478 | }); 479 | 480 | // 'a' 481 | // 'b' 482 | // 'c' 483 | // 'done' 484 | ``` 485 | 486 | ## Comparison Table 487 | 488 | You can reduce the CPU load by using the chillout.js API instead native JavaScript loops in your application. 489 | 490 | Examples: 491 | 492 | | JavaScript Statement | chillout | 493 | | -------------------------------------|-------------------------------------------------------------------------------| 494 | | [1, 2, 3].forEach(function(v, i) {}) | chillout.forEach([1, 2, 3], function(v, i) {}) | 495 | | for (i = 0; i < 5; i++) {} | chillout.repeat(5, function(i) {}) | 496 | | for (i = 10; i < 20; i += 2) {} | chillout.repeat({ start: 10, step: 2, done: 20 }, function(i) {}) | 497 | | while (true) {} | chillout.until(function() {}) | 498 | | while (cond()) {
  doSomething();
} | chillout.until(function() {
  if (!cond()) return chillout.StopIteration;
  doSomething();
}) | 499 | | for (value of [1, 2, 3]) {} | chillout.forOf([1, 2, 3], function(value) {}) | 500 | 501 | (You can write more simply using by `async / await` syntax because chillout.js' all APIs return Promise.) 502 | 503 | ## Contributing 504 | 505 | We're waiting for your pull requests and issues. 506 | Don't forget to execute `npm run test` before requesting. 507 | Accepted only requests without errors. 508 | 509 | ## License 510 | 511 | MIT 512 | --------------------------------------------------------------------------------