├── .travis.yml ├── .gitignore ├── package.json ├── test └── index.js ├── LICENSE ├── index.js └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rafor", 3 | "version": "1.0.2", 4 | "description": "RequestAnimationFrame friendly async for iterator", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "tap test/*.js" 8 | }, 9 | "keywords": [ 10 | "async", 11 | "for", 12 | "iterator", 13 | "requestanimationframe", 14 | "raf" 15 | ], 16 | "author": "Andrei Kashcha", 17 | "license": "MIT", 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/anvaka/rafor" 21 | }, 22 | "devDependencies": { 23 | "tap": "^1.3.2" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var test = require('tap').test; 2 | var asyncFor = require('../'); 3 | 4 | test('it can iterate array', function (t) { 5 | var array = [1, 2, 3, 4]; 6 | var totalVisits = 0; 7 | asyncFor(array, visit, done); 8 | 9 | function visit(element, index, a) { 10 | t.ok(a === array, 'Array is the same as the source array'); 11 | t.equals(element, a[index], 'Visited element @' + index + ' is correct'); 12 | totalVisits += 1; 13 | } 14 | 15 | function done(a) { 16 | t.ok(a === array, 'Done is called on non-empty array'); 17 | t.equals(totalVisits, array.length, 'All elements are visited'); 18 | t.end(); 19 | } 20 | }); 21 | 22 | test('it can iterate empty array', function (t) { 23 | var array = []; 24 | var totalCount = 0; 25 | asyncFor(array, visit, done); 26 | 27 | function visit(element, index, a) { 28 | t.fail('Cannot visit empty array'); 29 | } 30 | 31 | function done(a) { 32 | t.ok(a === array, 'Done is called on empty array'); 33 | t.end(); 34 | } 35 | }); 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2024 Andrei Kashcha 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 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = asyncFor; 2 | 3 | /** 4 | * Iterates over array in async manner. This function attempts to maximize 5 | * number of elements visited within single event loop cycle, while at the 6 | * same time tries to not exceed a time threshold allowed to stay within 7 | * event loop. 8 | * 9 | * @param {Array} array which needs to be iterated. Array-like objects are OK too. 10 | * @param {VisitCalback} visitCallback called for every element within for loop. 11 | * @param {DoneCallback} doneCallback called when iterator has reached end of array. 12 | * @param {Object=} options - additional configuration: 13 | * @param {number} [options.step=1] - default iteration step 14 | * @param {number} [options.maxTimeMS=8] - maximum time (in milliseconds) which 15 | * iterator should spend within single event loop. 16 | * @param {number} [options.probeElements=5000] - how many elements should iterator 17 | * visit to measure its iteration speed. 18 | */ 19 | function asyncFor(array, visitCallback, doneCallback, options) { 20 | var start = 0; 21 | var elapsed = 0; 22 | options = options || {}; 23 | var step = options.step || 1; 24 | var maxTimeMS = options.maxTimeMS || 8; 25 | var pointsPerLoopCycle = options.probeElements || 5000; 26 | // we should never block main thread for too long... 27 | setTimeout(processSubset, 0); 28 | 29 | function processSubset() { 30 | var finish = Math.min(array.length, start + pointsPerLoopCycle); 31 | var i = start; 32 | var timeStart = new Date(); 33 | for (i = start; i < finish; i += step) { 34 | visitCallback(array[i], i, array); 35 | } 36 | if (i < array.length) { 37 | elapsed += (new Date() - timeStart); 38 | start = i; 39 | 40 | pointsPerLoopCycle = Math.round(start * maxTimeMS/elapsed); 41 | setTimeout(processSubset, 0); 42 | } else { 43 | doneCallback(array); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rafor [![Build Status](https://travis-ci.org/anvaka/rafor.svg)](https://travis-ci.org/anvaka/rafor) 2 | 3 | This project will allow you to iterate over huge arrays asynchronously without 4 | impacting responsiveness of the application. 5 | 6 | # usage 7 | 8 | ``` js 9 | // Let's say you have a huge array, and you want to find its maximum 10 | // element. Once element is found you want to report it back to requestor: 11 | var asyncFor = require('rafor'); 12 | 13 | function findMaxElement(array, cb) { 14 | var max = Number.NEGATIVE_INFINITY; 15 | 16 | asyncFor(array, visit, done); 17 | 18 | function visit(el, index, array) { 19 | if (el > max) max = el; 20 | } 21 | 22 | function done(array) { 23 | cb(max); 24 | } 25 | } 26 | ``` 27 | 28 | The code above will attempt to limit its time spent within `visit()` function 29 | to `8 ms`. This will ensure that your main JavaScript thread is not 100% busy 30 | calculating maximum, and the browser still has time to do other operations. 31 | 32 | Unlike many other `async for` implementations, this iterator will attempt to 33 | maximize number of elements visited within single event loop cycle, while still 34 | limiting itself to a given time quota. 35 | 36 | ## Configuration 37 | 38 | If you want to change time quota of `8 ms` to something different, you can 39 | pass it as an optional argument: 40 | 41 | ``` js 42 | asyncFor(array, visit, done, { 43 | maxTimeMS: 5 // spend no more than 5 milliseconds on `visit()` 44 | }); 45 | ``` 46 | 47 | By default the iterator will visit every single element of your source array. 48 | If you want to change iteration step you can also pass it via configuration: 49 | 50 | ``` js 51 | asyncFor(array, visit, done, { 52 | step: 3 // Visit element 0, 3, 6, 9, 12, ... and so on 53 | }); 54 | ``` 55 | 56 | Finally, iterator takes its opportunity to measure speed of your `visit()` 57 | callback during the first event loop cycle. By default it assumes that visiting 58 | 5,000 elements should be fast enough to not impact responsiveness of the 59 | browser, but if this number is too high or too low for your case, please give 60 | iterator a hint: 61 | 62 | ``` js 63 | // Let's say our `visit()` is CPU intensive function, and we assume that 64 | // calling visit() five times will require 10 to 16 milliseconds (which 65 | // gives good FPS rate and responsiveess). We can tell the iterator, that 66 | // it can measure first five calls: 67 | asyncFor(array, visit, done, { 68 | probeElements: 5 69 | }); 70 | 71 | // The iterator will keep remeasuring performance of `visit()` callback on 72 | // every event loop cycle, and will adjust number of calls to `visit()` 73 | // based on collected data. 74 | ``` 75 | 76 | # install 77 | 78 | With [npm](https://npmjs.org) do: 79 | 80 | ``` 81 | npm install rafor 82 | ``` 83 | 84 | # license 85 | 86 | MIT 87 | --------------------------------------------------------------------------------