package/package.json 000644 000765 000024 0000001213 12137466627 013030 0 ustar 00 000000 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.md 000644 000765 000024 0000011561 12137466641 012024 0 ustar 00 000000 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.js 000644 000765 000024 0000002151 11703174062 012224 0 ustar 00 000000 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.js 000644 000765 000024 0000004123 11703174376 014030 0 ustar 00 000000 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.js 000644 000765 000024 0000000660 11703174376 014614 0 ustar 00 000000 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.js 000644 000765 000024 0000004167 11703173636 013261 0 ustar 00 000000 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-MIT 000644 000765 000024 0000002046 11634461760 012175 0 ustar 00 000000 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.js 000644 000765 000024 0000012611 11661235256 014522 0 ustar 00 000000 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();
}
};