package/package.json000644 000765 000024 0000001213 12137466627013030 0ustar00000000 000000 { "author": "\"Cowboy\" Ben Alman (http://benalman.com/)", "name": "async-foreach", "description": "An optionally-asynchronous forEach with an interesting interface.", "version": "0.1.3", "homepage": "http://github.com/cowboy/javascript-sync-async-foreach", "bugs": "https://github.com/cowboy/javascript-sync-async-foreach/issues", "repository": { "type": "git", "url": "git://github.com/cowboy/javascript-sync-async-foreach.git" }, "main": "lib/foreach", "engines": { "node": "*" }, "keywords": [ "array", "loop", "sync", "async", "foreach" ], "dependencies": {}, "devDependencies": {} } package/README.md000644 000765 000024 0000011561 12137466641012024 0ustar00000000 000000 # JavaScript Sync/Async forEach An optionally-asynchronous forEach with an interesting interface. ## Getting Started This code should work just fine in Node.js: First, install the module with: `npm install async-foreach` ```javascript var forEach = require('async-foreach').forEach; forEach(["a", "b", "c"], function(item, index, arr) { console.log("each", item, index, arr); }); // logs: // each a 0 ["a", "b", "c"] // each b 1 ["a", "b", "c"] // each c 2 ["a", "b", "c"] ``` Or in the browser: ```html ``` In the browser, you can attach the forEach method to any object. ```html ``` ## The General Idea (Why I thought this was worth sharing) The idea is to allow the callback to decide _at runtime_ whether the loop will be synchronous or asynchronous. By using `this` in a creative way (in situations where that value isn't already spoken for), an entire control API can be offered without over-complicating function signatures. ```javascript forEach(arr, function(item, index) { // Synchronous. }); forEach(arr, function(item, index) { // Only when `this.async` is called does iteration becomes asynchronous. The // loop won't be continued until the `done` function is executed. var done = this.async(); // Continue in one second. setTimeout(done, 1000); }); forEach(arr, function(item, index) { // Break out of synchronous iteration early by returning false. return index !== 1; }); forEach(arr, function(item, index) { // Break out of asynchronous iteration early... var done = this.async(); // ...by passing false to the done function. setTimeout(function() { done(index !== 1); }); }); ``` ## Examples See the unit tests for more examples. ```javascript // Generic "done" callback. function allDone(notAborted, arr) { console.log("done", notAborted, arr); } // Synchronous. forEach(["a", "b", "c"], function(item, index, arr) { console.log("each", item, index, arr); }, allDone); // logs: // each a 0 ["a", "b", "c"] // each b 1 ["a", "b", "c"] // each c 2 ["a", "b", "c"] // done true ["a", "b", "c"] // Synchronous with early abort. forEach(["a", "b", "c"], function(item, index, arr) { console.log("each", item, index, arr); if (item === "b") { return false; } }, allDone); // logs: // each a 0 ["a", "b", "c"] // each b 1 ["a", "b", "c"] // done false ["a", "b", "c"] // Asynchronous. forEach(["a", "b", "c"], function(item, index, arr) { console.log("each", item, index, arr); var done = this.async(); setTimeout(function() { done(); }, 500); }, allDone); // logs: // each a 0 ["a", "b", "c"] // each b 1 ["a", "b", "c"] // each c 2 ["a", "b", "c"] // done true ["a", "b", "c"] // Asynchronous with early abort. forEach(["a", "b", "c"], function(item, index, arr) { console.log("each", item, index, arr); var done = this.async(); setTimeout(function() { done(item !== "b"); }, 500); }, allDone); // logs: // each a 0 ["a", "b", "c"] // each b 1 ["a", "b", "c"] // done false ["a", "b", "c"] // Not actually asynchronous. forEach(["a", "b", "c"], function(item, index, arr) { console.log("each", item, index, arr); var done = this.async() done(); }, allDone); // logs: // each a 0 ["a", "b", "c"] // each b 1 ["a", "b", "c"] // each c 2 ["a", "b", "c"] // done true ["a", "b", "c"] // Not actually asynchronous with early abort. forEach(["a", "b", "c"], function(item, index, arr) { console.log("each", item, index, arr); var done = this.async(); done(item !== "b"); }, allDone); // logs: // each a 0 ["a", "b", "c"] // each b 1 ["a", "b", "c"] // done false ["a", "b", "c"] ``` ## Contributing In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [grunt](https://github.com/cowboy/grunt). _Also, please don't edit files in the "dist" subdirectory as they are generated via grunt. You'll find source code in the "lib" subdirectory!_ ## Release History 04/29/2013 v0.1.3 Removed hard Node.js version dependency. 11/17/2011 v0.1.2 Adding sparse array support. Invalid length properties are now sanitized. This closes issue #1 (like a boss). 11/11/2011 v0.1.1 Refactored code to be much simpler. Yay for unit tests! 11/11/2011 v0.1.0 Initial Release. ## License Copyright (c) 2012 "Cowboy" Ben Alman Licensed under the MIT license. package/grunt.js000644 000765 000024 0000002151 11703174062012224 0ustar00000000 000000 /*global config:true, task:true*/ config.init({ pkg: '', meta: { title: 'JavaScript Sync/Async forEach', license: ['MIT'], copyright: 'Copyright (c) 2012 "Cowboy" Ben Alman', banner: '/* {{meta.title}} - v{{pkg.version}} - {{today "m/d/yyyy"}}\n' + ' * {{pkg.homepage}}\n' + ' * {{{meta.copyright}}}; Licensed {{join meta.license}} */' }, concat: { 'dist/ba-foreach.js': ['', ''] }, min: { 'dist/ba-foreach.min.js': ['', 'dist/ba-foreach.js'] }, test: { files: ['test/**/*.js'] }, lint: { files: ['grunt.js', 'lib/**/*.js', 'test/**/*.js'] }, watch: { files: '', tasks: 'lint:files test:files' }, jshint: { options: { curly: true, eqeqeq: true, immed: true, latedef: true, newcap: true, noarg: true, sub: true, undef: true, eqnull: true }, globals: { exports: true } }, uglify: {} }); // Default task. task.registerTask('default', 'lint:files test:files concat min'); package/dist/ba-foreach.js000644 000765 000024 0000004123 11703174376014030 0ustar00000000 000000 /* JavaScript Sync/Async forEach - v0.1.2 - 1/10/2012 * http://github.com/cowboy/javascript-sync-async-foreach * Copyright (c) 2012 "Cowboy" Ben Alman; Licensed MIT */ (function(exports) { // Iterate synchronously or asynchronously. exports.forEach = function(arr, eachFn, doneFn) { var i = -1; // Resolve array length to a valid (ToUint32) number. var len = arr.length >>> 0; // This IIFE is called once now, and then again, by name, for each loop // iteration. (function next(result) { // This flag will be set to true if `this.async` is called inside the // eachFn` callback. var async; // Was false returned from the `eachFn` callback or passed to the // `this.async` done function? var abort = result === false; // Increment counter variable and skip any indices that don't exist. This // allows sparse arrays to be iterated. do { ++i; } while (!(i in arr) && i !== len); // Exit if result passed to `this.async` done function or returned from // the `eachFn` callback was false, or when done iterating. if (abort || i === len) { // If a `doneFn` callback was specified, invoke that now. Pass in a // boolean value representing "not aborted" state along with the array. if (doneFn) { doneFn(!abort, arr); } return; } // Invoke the `eachFn` callback, setting `this` inside the callback to a // custom object that contains one method, and passing in the array item, // index, and the array. result = eachFn.call({ // If `this.async` is called inside the `eachFn` callback, set the async // flag and return a function that can be used to continue iterating. async: function() { async = true; return next; } }, arr[i], i, arr); // If the async flag wasn't set, continue by calling `next` synchronously, // passing in the result of the `eachFn` callback. if (!async) { next(result); } }()); }; }(typeof exports === "object" && exports || this));package/dist/ba-foreach.min.js000644 000765 000024 0000000660 11703174376014614 0ustar00000000 000000 /* JavaScript Sync/Async forEach - v0.1.2 - 1/10/2012 * http://github.com/cowboy/javascript-sync-async-foreach * Copyright (c) 2012 "Cowboy" Ben Alman; Licensed MIT */ (function(a){a.forEach=function(a,b,c){var d=-1,e=a.length>>>0;(function f(g){var h,j=g===!1;do++d;while(!(d in a)&&d!==e);if(j||d===e){c&&c(!j,a);return}g=b.call({async:function(){return h=!0,f}},a[d],d,a),h||f(g)})()}})(typeof exports=="object"&&exports||this)package/lib/foreach.js000644 000765 000024 0000004167 11703173636013261 0ustar00000000 000000 /*! * Sync/Async forEach * https://github.com/cowboy/javascript-sync-async-foreach * * Copyright (c) 2012 "Cowboy" Ben Alman * Licensed under the MIT license. * http://benalman.com/about/license/ */ (function(exports) { // Iterate synchronously or asynchronously. exports.forEach = function(arr, eachFn, doneFn) { var i = -1; // Resolve array length to a valid (ToUint32) number. var len = arr.length >>> 0; // This IIFE is called once now, and then again, by name, for each loop // iteration. (function next(result) { // This flag will be set to true if `this.async` is called inside the // eachFn` callback. var async; // Was false returned from the `eachFn` callback or passed to the // `this.async` done function? var abort = result === false; // Increment counter variable and skip any indices that don't exist. This // allows sparse arrays to be iterated. do { ++i; } while (!(i in arr) && i !== len); // Exit if result passed to `this.async` done function or returned from // the `eachFn` callback was false, or when done iterating. if (abort || i === len) { // If a `doneFn` callback was specified, invoke that now. Pass in a // boolean value representing "not aborted" state along with the array. if (doneFn) { doneFn(!abort, arr); } return; } // Invoke the `eachFn` callback, setting `this` inside the callback to a // custom object that contains one method, and passing in the array item, // index, and the array. result = eachFn.call({ // If `this.async` is called inside the `eachFn` callback, set the async // flag and return a function that can be used to continue iterating. async: function() { async = true; return next; } }, arr[i], i, arr); // If the async flag wasn't set, continue by calling `next` synchronously, // passing in the result of the `eachFn` callback. if (!async) { next(result); } }()); }; }(typeof exports === "object" && exports || this));package/LICENSE-MIT000644 000765 000024 0000002046 11634461760012175 0ustar00000000 000000 Copyright (c) 2011 "Cowboy" Ben Alman Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package/test/foreach_test.js000644 000765 000024 0000012611 11661235256014522 0ustar00000000 000000 /*global require:true, setTimeout:true */ var forEach = require('../lib/foreach').forEach; exports['foreach'] = { setUp: function(done) { this.order = []; this.track = function() { [].push.apply(this.order, arguments); }; done(); }, 'Synchronous': function(test) { test.expect(1); var that = this; var arr = ["a", "b", "c"]; forEach(arr, function(item, index, arr) { that.track("each", item, index, arr); }); test.deepEqual(that.order, [ "each", "a", 0, arr, "each", "b", 1, arr, "each", "c", 2, arr ], "should call eachFn for each array item, in order."); test.done(); }, 'Synchronous, done': function(test) { test.expect(1); var that = this; var arr = ["a", "b", "c"]; forEach(arr, function(item, index, arr) { that.track("each", item, index, arr); }, function(notAborted, arr) { that.track("done", notAborted, arr); }); test.deepEqual(that.order, [ "each", "a", 0, arr, "each", "b", 1, arr, "each", "c", 2, arr, "done", true, arr ], "should call eachFn for each array item, in order, followed by doneFn."); test.done(); }, 'Synchronous, early abort': function(test) { test.expect(1); var that = this; var arr = ["a", "b", "c"]; forEach(arr, function(item, index, arr) { that.track("each", item, index, arr); if (item === "b") { return false; } }, function(notAborted, arr) { that.track("done", notAborted, arr); }); test.deepEqual(that.order, [ "each", "a", 0, arr, "each", "b", 1, arr, "done", false, arr ], "should call eachFn for each array item, in order, followed by doneFn."); test.done(); }, 'Asynchronous': function(test) { test.expect(1); var that = this; var arr = ["a", "b", "c"]; forEach(arr, function(item, index, arr) { that.track("each", item, index, arr); var done = this.async(); setTimeout(done, 10); }); setTimeout(function() { test.deepEqual(that.order, [ "each", "a", 0, arr, "each", "b", 1, arr, "each", "c", 2, arr ], "should call eachFn for each array item, in order."); test.done(); }, 100); }, 'Asynchronous, done': function(test) { test.expect(1); var that = this; var arr = ["a", "b", "c"]; forEach(arr, function(item, index, arr) { that.track("each", item, index, arr); var done = this.async(); setTimeout(done, 10); }, function(notAborted, arr) { that.track("done", notAborted, arr); test.deepEqual(that.order, [ "each", "a", 0, arr, "each", "b", 1, arr, "each", "c", 2, arr, "done", true, arr ], "should call eachFn for each array item, in order, followed by doneFn."); test.done(); }); }, 'Asynchronous, early abort': function(test) { test.expect(1); var that = this; var arr = ["a", "b", "c"]; forEach(arr, function(item, index, arr) { that.track("each", item, index, arr); var done = this.async(); setTimeout(function() { done(item !== "b"); }, 10); }, function(notAborted, arr) { that.track("done", notAborted, arr); test.deepEqual(that.order, [ "each", "a", 0, arr, "each", "b", 1, arr, "done", false, arr ], "should call eachFn for each array item, in order, followed by doneFn."); test.done(); }); }, 'Not actually asynchronous': function(test) { test.expect(1); var that = this; var arr = ["a", "b", "c"]; forEach(arr, function(item, index, arr) { that.track("each", item, index, arr); var done = this.async(); done(); }, function(notAborted, arr) { that.track("done", notAborted, arr); test.deepEqual(that.order, [ "each", "a", 0, arr, "each", "b", 1, arr, "each", "c", 2, arr, "done", true, arr ], "should call eachFn for each array item, in order, followed by doneFn."); test.done(); }); }, 'Not actually asynchronous, early abort': function(test) { test.expect(1); var that = this; var arr = ["a", "b", "c"]; forEach(arr, function(item, index, arr) { that.track("each", item, index, arr); var done = this.async(); done(item !== "b"); }, function(notAborted, arr) { that.track("done", notAborted, arr); test.deepEqual(that.order, [ "each", "a", 0, arr, "each", "b", 1, arr, "done", false, arr ], "should call eachFn for each array item, in order, followed by doneFn."); test.done(); }); }, 'Sparse array support': function(test) { test.expect(1); var that = this; var arr = []; arr[0] = "a"; arr[9] = "z"; forEach(arr, function(item, index, arr) { that.track("each", item, index, arr); }); test.deepEqual(that.order, [ "each", "a", 0, arr, "each", "z", 9, arr ], "should skip nonexistent array items."); test.done(); }, 'Invalid length sanitization': function(test) { test.expect(1); var that = this; var obj = {length: 4294967299, 0: "a", 2: "b", 3: "c" }; forEach(obj, function(item, index, arr) { that.track("each", item, index, arr); }); test.deepEqual(that.order, [ "each", "a", 0, obj, "each", "b", 2, obj ], "should sanitize length property (ToUint32)."); test.done(); } };