From 94531f7feae959cf5a8a9abf82efaa9d3141e0f0 Mon Sep 17 00:00:00 2001 From: Sven Slootweg Date: Mon, 14 Mar 2016 04:04:02 +0100 Subject: [PATCH] 1.0.0 --- README.md | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++ gulpfile.js | 24 +++++++++++++ index.js | 3 ++ lib/index.js | 27 +++++++++++++++ package.json | 34 +++++++++++++++++++ src/index.js | 27 +++++++++++++++ test.js | 25 ++++++++++++++ 7 files changed, 235 insertions(+) create mode 100644 README.md create mode 100644 gulpfile.js create mode 100644 index.js create mode 100644 lib/index.js create mode 100644 package.json create mode 100644 src/index.js create mode 100644 test.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..2644a31 --- /dev/null +++ b/README.md @@ -0,0 +1,95 @@ +# promise-while-loop + +An asynchronous while-loop implementation with full support for Promises - both for the main logic, and for the predicate function. + +## A word of caution + +Think __very carefully__ before using a while loop for asynchronous code. In many cases, it will be cleaner to simply *use recursion* - if the code is asynchronous, this will never cause stack overflows, no matter how many levels you nest. In fact, recursion is what this module uses internally; it's the only real way to implement asynchronous loops. + +A simple example of using recursion, __*without `promise-while-loop`*__: + +```javascript +var Promise = require("bluebird"); +var bhttp = require("bhttp"); + +function loop(pageNumber) { + return Promise.try(function() { + return bhttp.get("http://api.example.com/page/" + pageNumber); + }).then(function(response) { + if (response.headers["x-next-page"] != null) { + return Promise.try(function() { + return loop(response.headers["x-next-page"]); + }).then(function(recursiveResults) { + return [response.body].concat(recursiveResults); + }); + } + }); +} + +Promise.try(function() { + var i = 0; + + return loop(1); +}).then(function(results) { + // Now `results` is an array that contains the response for each HTTP request made. +}) +``` + +If you're sure that you can write cleaner code with a while loop than through recursion, then this is the module for you :) + +## License + +[WTFPL](http://www.wtfpl.net/txt/copying/) or [CC0](https://creativecommons.org/publicdomain/zero/1.0/), whichever you prefer. A donation and/or attribution are appreciated, but not required. + +## Donate + +My income consists largely of donations for my projects. If this module is useful to you, consider [making a donation](http://cryto.net/~joepie91/donate.html)! + +You can donate using Bitcoin, PayPal, Flattr, cash-in-mail, SEPA transfers, and pretty much anything else. + +## Contributing + +Pull requests welcome. Please make sure your modifications are in line with the overall code style, and ensure that you're editing the files in `src/`, not those in `lib/`. + +Build tool of choice is `gulp`; simply run `gulp` while developing, and it will watch for changes. + +Be aware that by making a pull request, you agree to release your modifications under the licenses stated above. + +## Usage + +A simple (contrived) example: + +```javascript +var i = 0; + +Promise.try(function() { + return whileLoop(function(lastResult) { + return Promise.try(function() { + return lastResult < 20; + }).delay(500); // Artificial delay, to prove that this really is asynchronous + }, function(lastResult) { + return Promise.try(function() { + var currentIteration = i; + i += 1; + + console.log("Last iteration: " + lastResult + " // Current iteration: " + currentIteration + " // Next iteration: " + i); + + return currentIteration; + }); + }); +}).then(function(results) { + console.log("i: " + i + " // Results: " + results) +}); + +``` + +## API + +### whileLoop(predicate, func) + +Initiates the loop. The loop will always run at least once - the `predicate` function will only be called *after* the first iteration. + +* __predicate__: This should be a function, returning either `true` or `false` (or a Promise that resolves to either of those) - depending on whether the loop should continue running or not. It receives a single argument, containing the result (return/resolve value) of the 'main function' from the last iteration. +* __func__: This should be a function, optionally returning a Promise or synchronous value. It's the 'main' function that contains the 'body' of the loop - ie. the code that is executed on each iteration. It also receives a single argument, with the result of the last operation - except for the first iteration, where it contains `undefined`, as there is no previous result to provide. + +The `whileLoop` call will return a Promise, __collect the results__ into an (ordered) array, and resolve with that array once the loop has finished. Any kind of rejection/thrown error, in either the `predicate` function or the main body, will immediately abort the loop, and reject the returned Promise with the error that caused it. diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..f35e142 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,24 @@ +var gulp = require('gulp'); + +var gutil = require('gulp-util'); +var babel = require('gulp-babel'); +var cache = require('gulp-cached'); +var remember = require('gulp-remember'); +var plumber = require('gulp-plumber'); + +var source = ["src/**/*.js"] + +gulp.task('babel', function() { + return gulp.src(source) + .pipe(plumber()) + .pipe(cache("babel")) + .pipe(babel({presets: ["es2015"]}).on('error', gutil.log)).on('data', gutil.log) + .pipe(remember("babel")) + .pipe(gulp.dest("lib/")); +}); + +gulp.task('watch', function () { + gulp.watch(source, ['babel']); +}); + +gulp.task('default', ['babel', 'watch']); \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..507257b --- /dev/null +++ b/index.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require("./lib"); \ No newline at end of file diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..704f1a3 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,27 @@ +'use strict'; + +var Promise = require("bluebird"); + +module.exports = function promiseWhile(predicate, func) { + var results = []; + + function iteration(value) { + return Promise.try(function () { + return func(value); + }).then(function (result) { + results.push(result); + + return Promise.try(function () { + return predicate(result); + }).then(function (predicateResult) { + if (predicateResult) { + return iteration(result); + } else { + return results; + } + }); + }); + } + + return iteration(); +}; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..66cf2ee --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "promise-while-loop", + "version": "1.0.0", + "description": "An asynchronous while-loop implementation with full support for Promises", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git://github.com/joepie91/node-promise-while-loop" + }, + "keywords": [ + "while", + "promises", + "bluebird", + "recursion", + "loop" + ], + "author": "Sven Slootweg", + "license": "WTFPL", + "dependencies": { + "bluebird": "^3.3.3" + }, + "devDependencies": { + "babel-preset-es2015": "^6.6.0", + "gulp": "^3.9.1", + "gulp-babel": "^6.1.2", + "gulp-cached": "^1.1.0", + "gulp-plumber": "^1.1.0", + "gulp-remember": "^0.3.0", + "gulp-util": "^3.0.7" + } +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..546ad71 --- /dev/null +++ b/src/index.js @@ -0,0 +1,27 @@ +'use strict'; + +const Promise = require("bluebird"); + +module.exports = function promiseWhile(predicate, func) { + let results = []; + + function iteration(value) { + return Promise.try(() => { + return func(value); + }).then((result) => { + results.push(result); + + return Promise.try(() => { + return predicate(result); + }).then((predicateResult) => { + if (predicateResult) { + return iteration(result); + } else { + return results; + } + }); + }); + } + + return iteration(); +} \ No newline at end of file diff --git a/test.js b/test.js new file mode 100644 index 0000000..553c109 --- /dev/null +++ b/test.js @@ -0,0 +1,25 @@ +'use strict'; + +const Promise = require("bluebird"); +const whileLoop = require("./"); + +let i = 0; + +Promise.try(() => { + return whileLoop((lastResult) => { + return Promise.try(() => { + return lastResult < 20; + }).delay(500); + }, (lastResult) => { + return Promise.try(() => { + let currentIteration = i; + i += 1; + + console.log(`Last iteration: ${lastResult} // Current iteration: ${currentIteration} // Next iteration: ${i}`); + + return currentIteration; + }); + }); +}).then((results) => { + console.log(`i: ${i} // Results: ${results}`) +})