diff --git a/README.md b/README.md index e5c8035..0070999 100644 --- a/README.md +++ b/README.md @@ -5,45 +5,45 @@ Analogous to `[].forEach`, but handles items asynchronously with a final callbac This is the most essential piece of the [`ArrayAsync`](https://github.com/FuturesJS/ArrayAsync) package. -v3.x - Diet Cola Edition ---- -As I do every few years, I decided to rewrite FuturesJS. -This year's remake is extremely lightweight. +v5.x +---- + +We jumped from 3.x to 5.x because I'm considering creating a backwards-and-forwards compatible 4.x that +uses AngularJS-style function introspection to allow for having the next param. +Straight up, that's probably a bad idea and waste of time so I hope I don't actually do it. Usage -=== - -It's as simple as you could guess: +----- ```javascript - // waits for one request to finish before beginning the next - forEachAsync(['dogs', 'cats', 'octocats'], function (next, element, index, array) { - getPics(element, next); - - // then after all of the elements have been handled - // the final callback fires to let you know it's all done - }).then(function () { - console.log('All requests have finished'); - }); + // EXAMPLE ASYNC FUNCTION - // where `getPics` might be an asynchronous web request such as this - function getPics(animal, cb) { - var flickerAPI = "http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?"; - $.getJSON( - flickerAPI - , { tags: thing - , tagmode: "any" - , format: "json" - , success: function (data) { - console.log('teh animals:', data); - } - , complete: cb - } - ); + function getPicsAsync(animal) { + var flickerApi = "http://api.flickr.com/services/feeds/photos_public.gne?tagmode=any&format=json&tags=" + animal; + + return requestAsync({ url: flickerApi }); } ``` +```javascript + forEachAsync(['dogs', 'cats', 'octocats'], function (element) { + return getPicsAsync(element); + }).then(function () { + // then after all of the elements have been handled + // the final callback fires to let you know it's all done + console.log('All requests have finished'); + }); +``` + +### Supplying your own Promises Implementation + +If native ES6 promises are not available, then you should supply your own Promises/A+ +implementation like so: + +```javascript + forEachAsync = forEachAsync.create(window.Promise || require('bluebird')); +``` Browser Installation === @@ -51,7 +51,7 @@ Browser Installation You can install from bower: ```bash -bower install forEachAsync +bower install --save forEachAsync@5.x ``` Or download the raw file from : @@ -71,35 +71,13 @@ wget https://raw.github.com/FuturesJS/forEachAsync/master/forEachAsync.js }()); ``` -Or you can build it alongside other libraries: - -```bash -npm install -g pakmanager -npm install forEachAsync --save -pakmanager -e browser build -``` - -```html - -``` - -```javascript -(function () { - 'use strict'; - - var forEachAsync = require('forEachAsync').forEachAsync - ; - - // do stuff ... -}()); -``` - +**Note**: If you need both 3.x/4.x and 5.x version of `forEachAsync` in the browser... good luck with that... Node Installation === ```bash -npm install --save forEachAsync@3.x +npm install --save forEachAsync@5.x ``` API @@ -111,7 +89,6 @@ Parameters * `array` Array of elements to iterate over * `callback` Function to execute for each element, takes 4 arguments - * `next` the function to call when the current element has been dealt with * `element` a single element of the aforementioned array * `index` the index of the current element * `array` the same array mentioned above diff --git a/examples/browser/foreachasync-settimeout.js b/examples/browser/foreachasync-settimeout.js index 52171b9..1d8f16e 100644 --- a/examples/browser/foreachasync-settimeout.js +++ b/examples/browser/foreachasync-settimeout.js @@ -11,14 +11,16 @@ window.addEventListener('load', function () { ; log('i', 'item', 'ms'); - forEachAsync([2, 11, 37, 42], function (next, item, i) { + forEachAsync([2, 11, 37, 42], function (item, i) { var ms = Math.floor(Math.random() * 1000) ; - setTimeout(function () { - log(i, item, ms); - next(); - }, ms); + return new Promise(function (resolve) { + setTimeout(function () { + log(i, item, ms); + resolve(); + }, ms); + }); }).then(function () { log('All Done'); }); diff --git a/examples/node/foreachasync-fs-readdir.js b/examples/node/foreachasync-fs-readdir.js index feedf6c..489012d 100644 --- a/examples/node/foreachasync-fs-readdir.js +++ b/examples/node/foreachasync-fs-readdir.js @@ -1,20 +1,20 @@ 'use strict'; -var fs = require('fs') +var PromiseA = require('bluebird') + , fs = PromiseA.promisifyAll(require('fs')) , forEachAsync = require('foreachasync').forEachAsync , path = require('path') , dirpath = path.join(__dirname, 'testfiles') ; fs.readdir(dirpath, function (err, nodes) { - forEachAsync(nodes, function (next, node) { + forEachAsync(nodes, function (node) { var filepath = path.join(dirpath, node) ; console.log(filepath); - fs.readFile(filepath, null, function (err, contents) { + return fs.readFileAsync(filepath, null).then(function (contents) { console.log(node, contents.length); - next(); }); }).then(function () { console.log('All Done!'); diff --git a/forEachAsync.js b/forEachAsync.js index 8790d78..52ff957 100644 --- a/forEachAsync.js +++ b/forEachAsync.js @@ -2,34 +2,89 @@ ;(function (exports) { 'use strict'; - function forEachAsync(arr, fn, thisArg) { - var dones = [] - , index = -1 - ; + var BREAK = {} + , exp = {} + ; - function next(BREAK, result) { - index += 1; + function create(PromiseA) { + PromiseA = PromiseA.Promise || PromiseA; - if (index === arr.length || BREAK === forEachAsync.__BREAK) { - dones.forEach(function (done) { - done.call(thisArg, result); + + function forEachAsync(arr, fn, thisArg) { + var result = PromiseA.resolve() + ; + + arr.forEach(function (item, k) { + result = result.then(function () { + + var ret + ; + + if (thisArg) { + ret = fn.call(thisArg, item, k, arr); + } else { + ret = result = fn(item, k, arr); + } + + if (!ret.then) { + ret = PromiseA.resolve(result); + } + + ret.then(function (val) { + if (val === forEachAsync.__BREAK) { + return PromiseA.reject(new Error('break')); + //throw new Error('break'); + } + + return val; + }); }); - return; - } + }); - fn.call(thisArg, next, arr[index], index, arr); + result.catch(function (e) { + if ('break' !== e.message) { + throw e; + } + }); + + return result; } - setTimeout(next, 4); + forEachAsync.__BREAK = BREAK; - return { - then: function (_done) { - dones.push(_done); - return this; - } - }; + return forEachAsync; } - forEachAsync.__BREAK = {}; - exports.forEachAsync = forEachAsync; + /* + exp = forEachAsync.forEachAsync = forEachAsync; + exports = exports.forEachAsync = forEachAsync.forEachAsycn = forEachAsync; + exports.create = forEachAsync.create = function () {}; + */ + + + try { + exp.forEachAsync = create(require('bluebird')); + } catch(e) { + if ('undefined' !== typeof PromiseA) { + exp.forEachAsync = create(Promise); + } else { + try { + exp.forEachAsync = create(require('es6-promise')); + } catch(e) { + try { + exp.forEachAsync = create(require('rsvp')); + } catch(e) { + console.warning('forEachAsync needs requires a promise implementation and your environment does not provide one.' + + '\nYou may provide your own by calling forEachAsync.create(Promise) with a PromiseA+ implementation' + ); + } + } + } + } + + exports.forEachAsync = exp.forEachAsync.forEachAsync = exp.forEachAsync || function () { + throw new Error("You did not supply a Promises/A+ implementation. See the warning above."); + }; + exports.forEachAsync.create = create; + }('undefined' !== typeof exports && exports || new Function('return this')())); diff --git a/package.json b/package.json index bae751a..8e0aa9a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "foreachasync", - "version": "3.0.0", - "description": "A node- and browser-ready async counterpart of Array.prototype.forEach", + "version": "5.0.0", + "description": "A node- and browser-ready async (now with promises) counterpart of Array.prototype.forEach", "homepage": "https://github.com/FuturesJS/forEachAsync", "main": "forEachAsync.js", "directories": { @@ -21,11 +21,18 @@ "forEachAsync", "async", "futures", + "promise", + "promises", "each" ], + "optionalDependencies": { + "bluebird": "^2.5.3" + }, "author": "AJ ONeal (http://coolaj86.com/)", "license": "Apache2", "bugs": { "url": "https://github.com/FuturesJS/forEachAsync/issues" + }, + "dependencies": { } } diff --git a/test-bluebird.js b/test-bluebird.js new file mode 100644 index 0000000..daa6f54 --- /dev/null +++ b/test-bluebird.js @@ -0,0 +1,32 @@ +(function () { + "use strict"; + + var PromiseA = require('bluebird') + , forEachAsync = require('./forEachAsync').forEachAsync + , context = {} + ; + + forEachAsync([0, 500, 70, 200, 400, 100], function (element, i, arr) { + // test that array order is as expected + console.log(element, 'is element', i, 'of', arr.length); + + // test that thisness is applied + this[element] = i; + + if (i > 2) { + // test that synchronous callbacks don't mess things up + return PromiseA.resolve(); + } else { + // test asynchronous callbacks + return new Promise(function (resolve/*, reject*/) { + setTimeout(resolve, element); + }); + } + }, context).then(function () { + // test that thisness carried + console.log('context', context); + }).then(function () { + // test then chaining + console.log("now wasn't that nice?"); + }); +}()); diff --git a/test.js b/test-native.js similarity index 52% rename from test.js rename to test-native.js index 7c1cfe9..2c5d7cf 100644 --- a/test.js +++ b/test-native.js @@ -1,10 +1,11 @@ (function () { "use strict"; - var forEachAsync = require('./forEachAsync').forEachAsync + var forEachAsync = require('./forEachAsync').forEachAsync.create(Promise) + , context = {} ; - forEachAsync([0, 500, 70, 200, 400, 100], function (next, element, i, arr) { + forEachAsync([0, 500, 70, 200, 400, 100], function (element, i, arr) { // test that array order is as expected console.log(element, 'is element', i, 'of', arr.length); @@ -13,17 +14,18 @@ if (i > 2) { // test that synchronous callbacks don't mess things up - next(); + return Promise.resolve(); } else { // test asynchronous callbacks - setTimeout(next, element); + return new Promise(function (resolve/*, reject*/) { + setTimeout(resolve, element); + }); } - }, {}).then(function () { - // test that thisness carries - console.log(this); + }, context).then(function () { + // test that thisness carried + console.log('context', context); }).then(function () { // test then chaining console.log("now wasn't that nice?"); }); - }());