├── LICENSE-MIT
├── README.md
├── dist
├── ba-foreach.js
└── ba-foreach.min.js
├── grunt.js
├── lib
└── foreach.js
├── package.json
└── test
└── foreach_test.js
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011 "Cowboy" Ben Alman
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JavaScript Sync/Async forEach
2 |
3 | An optionally-asynchronous forEach with an interesting interface.
4 |
5 | ## Getting Started
6 |
7 | This code should work just fine in Node.js:
8 |
9 | First, install the module with: `npm install async-foreach`
10 |
11 | ```javascript
12 | var forEach = require('async-foreach').forEach;
13 | forEach(["a", "b", "c"], function(item, index, arr) {
14 | console.log("each", item, index, arr);
15 | });
16 | // logs:
17 | // each a 0 ["a", "b", "c"]
18 | // each b 1 ["a", "b", "c"]
19 | // each c 2 ["a", "b", "c"]
20 | ```
21 |
22 | Or in the browser:
23 |
24 | ```html
25 |
26 |
35 | ```
36 |
37 | In the browser, you can attach the forEach method to any object.
38 |
39 | ```html
40 |
43 |
44 |
53 | ```
54 |
55 | ## The General Idea (Why I thought this was worth sharing)
56 |
57 | 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.
58 |
59 | ```javascript
60 | forEach(arr, function(item, index) {
61 | // Synchronous.
62 | });
63 |
64 | forEach(arr, function(item, index) {
65 | // Only when `this.async` is called does iteration becomes asynchronous. The
66 | // loop won't be continued until the `done` function is executed.
67 | var done = this.async();
68 | // Continue in one second.
69 | setTimeout(done, 1000);
70 | });
71 |
72 | forEach(arr, function(item, index) {
73 | // Break out of synchronous iteration early by returning false.
74 | return index !== 1;
75 | });
76 |
77 | forEach(arr, function(item, index) {
78 | // Break out of asynchronous iteration early...
79 | var done = this.async();
80 | // ...by passing false to the done function.
81 | setTimeout(function() {
82 | done(index !== 1);
83 | });
84 | });
85 | ```
86 |
87 | ## Examples
88 | See the unit tests for more examples.
89 |
90 | ```javascript
91 | // Generic "done" callback.
92 | function allDone(notAborted, arr) {
93 | console.log("done", notAborted, arr);
94 | }
95 |
96 | // Synchronous.
97 | forEach(["a", "b", "c"], function(item, index, arr) {
98 | console.log("each", item, index, arr);
99 | }, allDone);
100 | // logs:
101 | // each a 0 ["a", "b", "c"]
102 | // each b 1 ["a", "b", "c"]
103 | // each c 2 ["a", "b", "c"]
104 | // done true ["a", "b", "c"]
105 |
106 | // Synchronous with early abort.
107 | forEach(["a", "b", "c"], function(item, index, arr) {
108 | console.log("each", item, index, arr);
109 | if (item === "b") { return false; }
110 | }, allDone);
111 | // logs:
112 | // each a 0 ["a", "b", "c"]
113 | // each b 1 ["a", "b", "c"]
114 | // done false ["a", "b", "c"]
115 |
116 | // Asynchronous.
117 | forEach(["a", "b", "c"], function(item, index, arr) {
118 | console.log("each", item, index, arr);
119 | var done = this.async();
120 | setTimeout(function() {
121 | done();
122 | }, 500);
123 | }, allDone);
124 | // logs:
125 | // each a 0 ["a", "b", "c"]
126 | // each b 1 ["a", "b", "c"]
127 | // each c 2 ["a", "b", "c"]
128 | // done true ["a", "b", "c"]
129 |
130 | // Asynchronous with early abort.
131 | forEach(["a", "b", "c"], function(item, index, arr) {
132 | console.log("each", item, index, arr);
133 | var done = this.async();
134 | setTimeout(function() {
135 | done(item !== "b");
136 | }, 500);
137 | }, allDone);
138 | // logs:
139 | // each a 0 ["a", "b", "c"]
140 | // each b 1 ["a", "b", "c"]
141 | // done false ["a", "b", "c"]
142 |
143 | // Not actually asynchronous.
144 | forEach(["a", "b", "c"], function(item, index, arr) {
145 | console.log("each", item, index, arr);
146 | var done = this.async()
147 | done();
148 | }, allDone);
149 | // logs:
150 | // each a 0 ["a", "b", "c"]
151 | // each b 1 ["a", "b", "c"]
152 | // each c 2 ["a", "b", "c"]
153 | // done true ["a", "b", "c"]
154 |
155 | // Not actually asynchronous with early abort.
156 | forEach(["a", "b", "c"], function(item, index, arr) {
157 | console.log("each", item, index, arr);
158 | var done = this.async();
159 | done(item !== "b");
160 | }, allDone);
161 | // logs:
162 | // each a 0 ["a", "b", "c"]
163 | // each b 1 ["a", "b", "c"]
164 | // done false ["a", "b", "c"]
165 | ```
166 |
167 | ## Contributing
168 | 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).
169 |
170 | _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!_
171 |
172 | ## Release History
173 |
174 | 04/29/2013
175 | v0.1.3
176 | Removed hard Node.js version dependency.
177 |
178 | 11/17/2011
179 | v0.1.2
180 | Adding sparse array support.
181 | Invalid length properties are now sanitized.
182 | This closes issue #1 (like a boss).
183 |
184 | 11/11/2011
185 | v0.1.1
186 | Refactored code to be much simpler. Yay for unit tests!
187 |
188 | 11/11/2011
189 | v0.1.0
190 | Initial Release.
191 |
192 | ## License
193 | Copyright (c) 2012 "Cowboy" Ben Alman
194 | Licensed under the MIT license.
195 |
196 |
--------------------------------------------------------------------------------
/dist/ba-foreach.js:
--------------------------------------------------------------------------------
1 | /* JavaScript Sync/Async forEach - v0.1.2 - 1/10/2012
2 | * http://github.com/cowboy/javascript-sync-async-foreach
3 | * Copyright (c) 2012 "Cowboy" Ben Alman; Licensed MIT */
4 |
5 | (function(exports) {
6 |
7 | // Iterate synchronously or asynchronously.
8 | exports.forEach = function(arr, eachFn, doneFn) {
9 | var i = -1;
10 | // Resolve array length to a valid (ToUint32) number.
11 | var len = arr.length >>> 0;
12 |
13 | // This IIFE is called once now, and then again, by name, for each loop
14 | // iteration.
15 | (function next(result) {
16 | // This flag will be set to true if `this.async` is called inside the
17 | // eachFn` callback.
18 | var async;
19 | // Was false returned from the `eachFn` callback or passed to the
20 | // `this.async` done function?
21 | var abort = result === false;
22 |
23 | // Increment counter variable and skip any indices that don't exist. This
24 | // allows sparse arrays to be iterated.
25 | do { ++i; } while (!(i in arr) && i !== len);
26 |
27 | // Exit if result passed to `this.async` done function or returned from
28 | // the `eachFn` callback was false, or when done iterating.
29 | if (abort || i === len) {
30 | // If a `doneFn` callback was specified, invoke that now. Pass in a
31 | // boolean value representing "not aborted" state along with the array.
32 | if (doneFn) {
33 | doneFn(!abort, arr);
34 | }
35 | return;
36 | }
37 |
38 | // Invoke the `eachFn` callback, setting `this` inside the callback to a
39 | // custom object that contains one method, and passing in the array item,
40 | // index, and the array.
41 | result = eachFn.call({
42 | // If `this.async` is called inside the `eachFn` callback, set the async
43 | // flag and return a function that can be used to continue iterating.
44 | async: function() {
45 | async = true;
46 | return next;
47 | }
48 | }, arr[i], i, arr);
49 |
50 | // If the async flag wasn't set, continue by calling `next` synchronously,
51 | // passing in the result of the `eachFn` callback.
52 | if (!async) {
53 | next(result);
54 | }
55 | }());
56 | };
57 |
58 | }(typeof exports === "object" && exports || this));
--------------------------------------------------------------------------------
/dist/ba-foreach.min.js:
--------------------------------------------------------------------------------
1 | /* JavaScript Sync/Async forEach - v0.1.2 - 1/10/2012
2 | * http://github.com/cowboy/javascript-sync-async-foreach
3 | * Copyright (c) 2012 "Cowboy" Ben Alman; Licensed MIT */
4 | (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)
--------------------------------------------------------------------------------
/grunt.js:
--------------------------------------------------------------------------------
1 | /*global config:true, task:true*/
2 | config.init({
3 | pkg: '',
4 | meta: {
5 | title: 'JavaScript Sync/Async forEach',
6 | license: ['MIT'],
7 | copyright: 'Copyright (c) 2012 "Cowboy" Ben Alman',
8 | banner: '/* {{meta.title}} - v{{pkg.version}} - {{today "m/d/yyyy"}}\n' +
9 | ' * {{pkg.homepage}}\n' +
10 | ' * {{{meta.copyright}}}; Licensed {{join meta.license}} */'
11 | },
12 | concat: {
13 | 'dist/ba-foreach.js': ['', '']
14 | },
15 | min: {
16 | 'dist/ba-foreach.min.js': ['', 'dist/ba-foreach.js']
17 | },
18 | test: {
19 | files: ['test/**/*.js']
20 | },
21 | lint: {
22 | files: ['grunt.js', 'lib/**/*.js', 'test/**/*.js']
23 | },
24 | watch: {
25 | files: '',
26 | tasks: 'lint:files test:files'
27 | },
28 | jshint: {
29 | options: {
30 | curly: true,
31 | eqeqeq: true,
32 | immed: true,
33 | latedef: true,
34 | newcap: true,
35 | noarg: true,
36 | sub: true,
37 | undef: true,
38 | eqnull: true
39 | },
40 | globals: {
41 | exports: true
42 | }
43 | },
44 | uglify: {}
45 | });
46 |
47 | // Default task.
48 | task.registerTask('default', 'lint:files test:files concat min');
49 |
--------------------------------------------------------------------------------
/lib/foreach.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Sync/Async forEach
3 | * https://github.com/cowboy/javascript-sync-async-foreach
4 | *
5 | * Copyright (c) 2012 "Cowboy" Ben Alman
6 | * Licensed under the MIT license.
7 | * http://benalman.com/about/license/
8 | */
9 |
10 | (function(exports) {
11 |
12 | // Iterate synchronously or asynchronously.
13 | exports.forEach = function(arr, eachFn, doneFn) {
14 | var i = -1;
15 | // Resolve array length to a valid (ToUint32) number.
16 | var len = arr.length >>> 0;
17 |
18 | // This IIFE is called once now, and then again, by name, for each loop
19 | // iteration.
20 | (function next(result) {
21 | // This flag will be set to true if `this.async` is called inside the
22 | // eachFn` callback.
23 | var async;
24 | // Was false returned from the `eachFn` callback or passed to the
25 | // `this.async` done function?
26 | var abort = result === false;
27 |
28 | // Increment counter variable and skip any indices that don't exist. This
29 | // allows sparse arrays to be iterated.
30 | do { ++i; } while (!(i in arr) && i !== len);
31 |
32 | // Exit if result passed to `this.async` done function or returned from
33 | // the `eachFn` callback was false, or when done iterating.
34 | if (abort || i === len) {
35 | // If a `doneFn` callback was specified, invoke that now. Pass in a
36 | // boolean value representing "not aborted" state along with the array.
37 | if (doneFn) {
38 | doneFn(!abort, arr);
39 | }
40 | return;
41 | }
42 |
43 | // Invoke the `eachFn` callback, setting `this` inside the callback to a
44 | // custom object that contains one method, and passing in the array item,
45 | // index, and the array.
46 | result = eachFn.call({
47 | // If `this.async` is called inside the `eachFn` callback, set the async
48 | // flag and return a function that can be used to continue iterating.
49 | async: function() {
50 | async = true;
51 | return next;
52 | }
53 | }, arr[i], i, arr);
54 |
55 | // If the async flag wasn't set, continue by calling `next` synchronously,
56 | // passing in the result of the `eachFn` callback.
57 | if (!async) {
58 | next(result);
59 | }
60 | }());
61 | };
62 |
63 | }(typeof exports === "object" && exports || this));
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "\"Cowboy\" Ben Alman (http://benalman.com/)",
3 | "name": "async-foreach",
4 | "description": "An optionally-asynchronous forEach with an interesting interface.",
5 | "version": "0.1.3",
6 | "homepage": "http://github.com/cowboy/javascript-sync-async-foreach",
7 | "bugs": "https://github.com/cowboy/javascript-sync-async-foreach/issues",
8 | "repository": {
9 | "type": "git",
10 | "url": "git://github.com/cowboy/javascript-sync-async-foreach.git"
11 | },
12 | "main": "lib/foreach",
13 | "engines": {
14 | "node": "*"
15 | },
16 | "keywords": [
17 | "array",
18 | "loop",
19 | "sync",
20 | "async",
21 | "foreach"
22 | ],
23 | "dependencies": {},
24 | "devDependencies": {}
25 | }
26 |
--------------------------------------------------------------------------------
/test/foreach_test.js:
--------------------------------------------------------------------------------
1 | /*global require:true, setTimeout:true */
2 | var forEach = require('../lib/foreach').forEach;
3 |
4 | exports['foreach'] = {
5 | setUp: function(done) {
6 | this.order = [];
7 | this.track = function() {
8 | [].push.apply(this.order, arguments);
9 | };
10 | done();
11 | },
12 | 'Synchronous': function(test) {
13 | test.expect(1);
14 | var that = this;
15 |
16 | var arr = ["a", "b", "c"];
17 | forEach(arr, function(item, index, arr) {
18 | that.track("each", item, index, arr);
19 | });
20 |
21 | test.deepEqual(that.order, [
22 | "each", "a", 0, arr,
23 | "each", "b", 1, arr,
24 | "each", "c", 2, arr
25 | ], "should call eachFn for each array item, in order.");
26 | test.done();
27 | },
28 | 'Synchronous, done': function(test) {
29 | test.expect(1);
30 | var that = this;
31 |
32 | var arr = ["a", "b", "c"];
33 | forEach(arr, function(item, index, arr) {
34 | that.track("each", item, index, arr);
35 | }, function(notAborted, arr) {
36 | that.track("done", notAborted, arr);
37 | });
38 |
39 | test.deepEqual(that.order, [
40 | "each", "a", 0, arr,
41 | "each", "b", 1, arr,
42 | "each", "c", 2, arr,
43 | "done", true, arr
44 | ], "should call eachFn for each array item, in order, followed by doneFn.");
45 | test.done();
46 | },
47 | 'Synchronous, early abort': function(test) {
48 | test.expect(1);
49 | var that = this;
50 |
51 | var arr = ["a", "b", "c"];
52 | forEach(arr, function(item, index, arr) {
53 | that.track("each", item, index, arr);
54 | if (item === "b") { return false; }
55 | }, function(notAborted, arr) {
56 | that.track("done", notAborted, arr);
57 | });
58 |
59 | test.deepEqual(that.order, [
60 | "each", "a", 0, arr,
61 | "each", "b", 1, arr,
62 | "done", false, arr
63 | ], "should call eachFn for each array item, in order, followed by doneFn.");
64 | test.done();
65 | },
66 | 'Asynchronous': function(test) {
67 | test.expect(1);
68 | var that = this;
69 |
70 | var arr = ["a", "b", "c"];
71 | forEach(arr, function(item, index, arr) {
72 | that.track("each", item, index, arr);
73 | var done = this.async();
74 | setTimeout(done, 10);
75 | });
76 |
77 | setTimeout(function() {
78 | test.deepEqual(that.order, [
79 | "each", "a", 0, arr,
80 | "each", "b", 1, arr,
81 | "each", "c", 2, arr
82 | ], "should call eachFn for each array item, in order.");
83 | test.done();
84 | }, 100);
85 | },
86 | 'Asynchronous, done': function(test) {
87 | test.expect(1);
88 | var that = this;
89 |
90 | var arr = ["a", "b", "c"];
91 | forEach(arr, function(item, index, arr) {
92 | that.track("each", item, index, arr);
93 | var done = this.async();
94 | setTimeout(done, 10);
95 | }, function(notAborted, arr) {
96 | that.track("done", notAborted, arr);
97 | test.deepEqual(that.order, [
98 | "each", "a", 0, arr,
99 | "each", "b", 1, arr,
100 | "each", "c", 2, arr,
101 | "done", true, arr
102 | ], "should call eachFn for each array item, in order, followed by doneFn.");
103 | test.done();
104 | });
105 | },
106 | 'Asynchronous, early abort': function(test) {
107 | test.expect(1);
108 | var that = this;
109 |
110 | var arr = ["a", "b", "c"];
111 | forEach(arr, function(item, index, arr) {
112 | that.track("each", item, index, arr);
113 | var done = this.async();
114 | setTimeout(function() {
115 | done(item !== "b");
116 | }, 10);
117 | }, function(notAborted, arr) {
118 | that.track("done", notAborted, arr);
119 | test.deepEqual(that.order, [
120 | "each", "a", 0, arr,
121 | "each", "b", 1, arr,
122 | "done", false, arr
123 | ], "should call eachFn for each array item, in order, followed by doneFn.");
124 | test.done();
125 | });
126 | },
127 | 'Not actually asynchronous': function(test) {
128 | test.expect(1);
129 | var that = this;
130 |
131 | var arr = ["a", "b", "c"];
132 | forEach(arr, function(item, index, arr) {
133 | that.track("each", item, index, arr);
134 | var done = this.async();
135 | done();
136 | }, function(notAborted, arr) {
137 | that.track("done", notAborted, arr);
138 | test.deepEqual(that.order, [
139 | "each", "a", 0, arr,
140 | "each", "b", 1, arr,
141 | "each", "c", 2, arr,
142 | "done", true, arr
143 | ], "should call eachFn for each array item, in order, followed by doneFn.");
144 | test.done();
145 | });
146 | },
147 | 'Not actually asynchronous, early abort': function(test) {
148 | test.expect(1);
149 | var that = this;
150 |
151 | var arr = ["a", "b", "c"];
152 | forEach(arr, function(item, index, arr) {
153 | that.track("each", item, index, arr);
154 | var done = this.async();
155 | done(item !== "b");
156 | }, function(notAborted, arr) {
157 | that.track("done", notAborted, arr);
158 | test.deepEqual(that.order, [
159 | "each", "a", 0, arr,
160 | "each", "b", 1, arr,
161 | "done", false, arr
162 | ], "should call eachFn for each array item, in order, followed by doneFn.");
163 | test.done();
164 | });
165 | },
166 | 'Sparse array support': function(test) {
167 | test.expect(1);
168 | var that = this;
169 |
170 | var arr = [];
171 | arr[0] = "a";
172 | arr[9] = "z";
173 |
174 | forEach(arr, function(item, index, arr) {
175 | that.track("each", item, index, arr);
176 | });
177 |
178 | test.deepEqual(that.order, [
179 | "each", "a", 0, arr,
180 | "each", "z", 9, arr
181 | ], "should skip nonexistent array items.");
182 | test.done();
183 | },
184 | 'Invalid length sanitization': function(test) {
185 | test.expect(1);
186 | var that = this;
187 |
188 | var obj = {length: 4294967299, 0: "a", 2: "b", 3: "c" };
189 |
190 | forEach(obj, function(item, index, arr) {
191 | that.track("each", item, index, arr);
192 | });
193 |
194 | test.deepEqual(that.order, [
195 | "each", "a", 0, obj,
196 | "each", "b", 2, obj
197 | ], "should sanitize length property (ToUint32).");
198 | test.done();
199 | }
200 | };
201 |
--------------------------------------------------------------------------------