Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

initial import

  • Loading branch information...
commit 95d2c53b0102b718e771a895ddb834c909c83b0b 0 parents
tasogarepg authored
22 LICENSE
... ... @@ -0,0 +1,22 @@
  1 +(The MIT License)
  2 +
  3 +Copyright (c) 2012 tasogarepg <tasogare.pg@gmail.com>
  4 +
  5 +Permission is hereby granted, free of charge, to any person obtaining
  6 +a copy of this software and associated documentation files (the
  7 +'Software'), to deal in the Software without restriction, including
  8 +without limitation the rights to use, copy, modify, merge, publish,
  9 +distribute, sublicense, and/or sell copies of the Software, and to
  10 +permit persons to whom the Software is furnished to do so, subject to
  11 +the following conditions:
  12 +
  13 +The above copyright notice and this permission notice shall be
  14 +included in all copies or substantial portions of the Software.
  15 +
  16 +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
  17 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  18 +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  19 +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  20 +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  21 +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  22 +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
144 README.md
Source Rendered
... ... @@ -0,0 +1,144 @@
  1 +# node-block
  2 +
  3 +An async contrl-flow library for Node.js.
  4 +Easily parallel execution and error handling.
  5 +
  6 +## Installation
  7 +
  8 + $ npm install node-block
  9 +
  10 +## Example
  11 +
  12 +```js
  13 +var fs = require('fs');
  14 +var block = require('node-block.js').block;
  15 +
  16 +block(
  17 + function() {
  18 + fs.readFile('/path/to/file1', 'utf8', this.async('d1'));
  19 + fs.readFile('/path/to/file2', 'utf8', this.async('d2'));
  20 + },
  21 + function() {
  22 + var str = this.data.d1 + this.data.d2;
  23 + console.log(str);
  24 + }
  25 +)();
  26 +```
  27 +
  28 +### Error handling
  29 +
  30 +Function name is `cat` and `fin`.
  31 +
  32 +```js
  33 +var fs = require('fs');
  34 +var block = require('node-block.js').block;
  35 +
  36 +block(
  37 + function() {
  38 + fs.readFile('/path/to/file1', 'utf8', this.async('d1'));
  39 + fs.readFile('/path/to/file2', 'utf8', this.async('d2'));
  40 + },
  41 + function() {
  42 + this.data.d3 = this.data.d1 + this.data.d2;
  43 + },
  44 + function cat(e) { // catch
  45 + console.log(e);
  46 + throw e;
  47 + },
  48 + function fin() { // finally
  49 + console.log('fin'); // always run
  50 + }
  51 +)();
  52 +```
  53 +
  54 +### Jump to end
  55 +
  56 +call `this.end()` with return.
  57 +
  58 +```js
  59 +var fs = require('fs');
  60 +var block = require('node-block.js').block;
  61 +
  62 +block(
  63 + function() {
  64 + if (true) {
  65 + return this.end(); // called with return
  66 + }
  67 + },
  68 + function() {
  69 + // not run here.
  70 + },
  71 + function cat(e) { // catch
  72 + // when errorless, not run here.
  73 + },
  74 + function fin() { // finally
  75 + console.log('fin');
  76 + }
  77 +)();
  78 +```
  79 +
  80 +### Callback
  81 +
  82 +sample() is called after fin().
  83 +
  84 +```js
  85 +var fs = require('fs');
  86 +var block = require('node-block.js').block;
  87 +
  88 +block(
  89 + function() {
  90 + fs.readFile('/path/to/file1', 'utf8', this.async('d1'));
  91 + },
  92 + function fin() {
  93 + console.log('fin');
  94 + }
  95 +)(sample);
  96 +
  97 +function sample(err){
  98 + console.log(this.data.d1);
  99 +}
  100 +```
  101 +
  102 +### Nesting
  103 +
  104 +```js
  105 +var fs = require('fs');
  106 +var block = require('node-block.js').block;
  107 +
  108 +block(
  109 + function() {
  110 + fs.readFile('/path/to/file1', 'utf8', this.async('d1'));
  111 + fs.readFile('/path/to/file2', 'utf8', this.async('d2'));
  112 + },
  113 + function() {
  114 + fs.readFile('/path/to/file3', 'utf8', this.async('d3'));
  115 + block(
  116 + function() {
  117 + fs.readFile('/path/to/file4', 'utf8', this.async('e1'));
  118 + fs.readFile('/path/to/file5', 'utf8', this.async('e2'));
  119 + },
  120 + function() {
  121 + fs.readFile('/path/to/file6', 'utf8', this.async('e3'));
  122 + }
  123 + )(this.async('d4'));
  124 + },
  125 + function() {
  126 + var str = this.data.d1 + this.data.d2 + this.data.d3 +
  127 + this.data.d4.e1 + this.data.d4.e2 + this.data.d4.e3;
  128 + console.log(str);
  129 + }
  130 +)();
  131 +```
  132 +
  133 +## About API
  134 +
  135 +### this.async([String dataName])
  136 +Make a callback for async function. [String dataName] is name of setting result data. It's named arbitrarily.
  137 +
  138 +### this.end([Error err])
  139 +Jump to end. If [Error err] is set jump to cat(), else jump to fin() or callback. If cat(), fin(), callback don't exist , exit block.
  140 +This api must be called with 'return'.
  141 +
  142 +## License
  143 +
  144 +The MIT License
132 lib/node-block.js
... ... @@ -0,0 +1,132 @@
  1 +'use strict';
  2 +
  3 +exports.block = block;
  4 +
  5 +function block() {
  6 + var funcs = Array.prototype.slice.call(arguments);
  7 + var b = new BlockInfo(funcs);
  8 + return b.start.bind(b);
  9 +}
  10 +
  11 +function BlockInfo(funcs) {
  12 + this.stepList = [];
  13 + this.data = [];
  14 + this.cat = null;
  15 + this.fin = null;
  16 + this.cb = null;
  17 + var that = this;
  18 + funcs.forEach(function(func) {
  19 + var step = new StepInfo(that, that.stepList.length, func);
  20 + that.stepList.push(step);
  21 + if (func.name == 'cat') {
  22 + that.cat = step;
  23 + that.cat.func = function(err, data) {
  24 + if (!err) return;
  25 + func(err, data);
  26 + this.errBaton = null;
  27 + };
  28 + } else if (func.name == 'fin') {
  29 + that.fin = step;
  30 + }
  31 + });
  32 +}
  33 +
  34 +BlockInfo.prototype.start = function(cb) {
  35 + if (this.cb) {
  36 + this.stepList.pop();
  37 + this.cb = null;
  38 + }
  39 + if (cb) {
  40 + this.cb = new StepInfo(this, this.stepList.length, cb);
  41 + this.stepList.push(this.cb);
  42 + }
  43 + this.next(null, 0);
  44 +};
  45 +
  46 +BlockInfo.prototype.next = function(err, stepIndex) {
  47 + if (stepIndex >= this.stepList.length) {
  48 + return;
  49 + }
  50 + var step = this.stepList[stepIndex];
  51 + if (step.isDone) return;
  52 + step.isDone = true;
  53 + step.errBaton = err;
  54 + step.nextLock = true;
  55 + try {
  56 + step.run();
  57 + } catch (e) {
  58 + step.end(e);
  59 + return;
  60 + }
  61 + step.nextLock = false;
  62 + if (step.asyncCount <= 0 && step.endCount == 0) {
  63 + this.next(step.errBaton, step.stepIndex+1);
  64 + }
  65 +};
  66 +
  67 +BlockInfo.prototype.end = function(err) {
  68 + err = err || null;
  69 + if (err && this.cat && !this.cat.isDone) {
  70 + this.next(err, this.cat.stepIndex);
  71 + } else if (this.fin && !this.fin.isDone) {
  72 + this.next(err, this.fin.stepIndex);
  73 + } else if (this.cb && !this.cb.isDone) {
  74 + this.next(err, this.cb.stepIndex);
  75 + } else {
  76 + if (err) throw err;
  77 + }
  78 +};
  79 +
  80 +function StepInfo(block, stepIndex, func) {
  81 + this.block = block;
  82 + this.stepIndex = stepIndex;
  83 + this.asyncCount = 0;
  84 + this.endCount = 0;
  85 + this.isDone = false;
  86 + this.nextLock = false;
  87 + this.errBaton = null;
  88 + this.data = block.data;
  89 + this.func = func;
  90 +}
  91 +
  92 +StepInfo.prototype.run = function() {
  93 + this.func(this.errBaton, this.data);
  94 +};
  95 +
  96 +StepInfo.prototype.end = function(err) {
  97 + if (this.endCount++ === 0) {
  98 + this.block.end(err);
  99 + }
  100 +};
  101 +
  102 +StepInfo.prototype.async = function(dataName) {
  103 + this.asyncCount++;
  104 + var that = new AsyncInfo(this, dataName);
  105 + return function(err) {
  106 + var step = that.step;
  107 + var block = step.block;
  108 + if (err) {
  109 + step.end(err);
  110 + return;
  111 + }
  112 + if (that.dataName) {
  113 + var result = Array.prototype.slice.call(arguments, 1);
  114 + if (result.length === 0) {
  115 + block.data[that.dataName] = null;
  116 + } else if (result.length === 1) {
  117 + block.data[that.dataName] = result[0];
  118 + } else {
  119 + block.data[that.dataName] = result;
  120 + }
  121 + }
  122 + step.asyncCount--;
  123 + if (step.asyncCount <= 0 && !step.nextLock) {
  124 + block.next(step.errBaton, step.stepIndex+1);
  125 + }
  126 + };
  127 +};
  128 +
  129 +function AsyncInfo(step, dataName) {
  130 + this.step = step;
  131 + this.dataName = dataName || null;
  132 +}
15 package.json
... ... @@ -0,0 +1,15 @@
  1 +{
  2 + "name": "node-block",
  3 + "version": "0.1.0",
  4 + "description": "An async contrl-flow library for Node.js. Parallelable and Nestable.",
  5 + "keywords": ["control-flow"],
  6 + "author": "tasogarepg <tasogare.pg@gmail.com>",
  7 + "homepage": "http://github.com/tasogarepg/node-block",
  8 + "repository": "git://github.com/tasogarepg/node-block.git",
  9 + "main": "./lib/node-block.js",
  10 + "engines": { "node": ">= 0.4.0" },
  11 + "dependencies": {},
  12 + "devDependencies": {
  13 + "mocha": "*"
  14 + }
  15 +}
3  test/mocha.opts
... ... @@ -0,0 +1,3 @@
  1 +--ui bdd
  2 +--reporter spec
  3 +--timeout 5000
381 test/node-block.test.js
... ... @@ -0,0 +1,381 @@
  1 +var assert = require('assert');
  2 +var fs = require('fs');
  3 +var path = require('path');
  4 +var block = require('../lib/node-block.js').block;
  5 +
  6 +var fileA = path.join(__dirname, 'rsc', '_a.txt'); // aaaa
  7 +var fileB = path.join(__dirname, 'rsc', '_b.txt'); // bbbb
  8 +
  9 +describe('node-block', function() {
  10 +
  11 + it('sync chain', function(done) {
  12 + block(
  13 + function() {
  14 + this.data.d1 = 'a';
  15 + },
  16 + function() {
  17 + this.data.d1 += 'b';
  18 + },
  19 + function() {
  20 + assert.equal(this.data.d1, 'ab');
  21 + }
  22 + )(done);
  23 + });
  24 +
  25 + it('async chain serial', function(done) {
  26 + block(
  27 + function() {
  28 + fs.readFile(fileA, 'utf8', this.async('d1'));
  29 + },
  30 + function() {
  31 + fs.readFile(fileB, 'utf8', this.async('d2'));
  32 + },
  33 + function() {
  34 + var str = this.data.d1 + this.data.d2;
  35 + assert.equal(str, 'aaaabbbb');
  36 + }
  37 + )(done);
  38 + });
  39 +
  40 + it('async chain parallel', function(done) {
  41 + block(
  42 + function() {
  43 + fs.readFile(fileA, 'utf8', this.async('d1'));
  44 + fs.readFile(fileB, 'utf8', this.async('d2'));
  45 + },
  46 + function() {
  47 + fs.readFile(fileA, 'utf8', this.async('d3'));
  48 + fs.readFile(fileB, 'utf8', this.async('d4'));
  49 + },
  50 + function() {
  51 + var str = this.data.d1 + this.data.d2 + this.data.d3 + this.data.d4;
  52 + assert.equal(str, 'aaaabbbbaaaabbbb');
  53 + }
  54 + )(done);
  55 + });
  56 +
  57 + it('async parallel set time', function(done) {
  58 + block(
  59 + function() {
  60 + this.data.d1 = '';
  61 + var that = this;
  62 + var cb1 = this.async();
  63 + setTimeout(function() {
  64 + that.data.d1 += 'a';
  65 + cb1();
  66 + }, 40);
  67 + var cb2 = this.async();
  68 + setTimeout(function() {
  69 + that.data.d1 += 'b';
  70 + cb2();
  71 + }, 20);
  72 + var cb3 = this.async();
  73 + setTimeout(function() {
  74 + that.data.d1 += 'c';
  75 + cb3();
  76 + }, 1);
  77 + },
  78 + function() {
  79 + assert.equal(this.data.d1, 'cba');
  80 + }
  81 + )(done);
  82 + });
  83 +
  84 + it('async chain no delay', function(done) {
  85 + block(
  86 + function() {
  87 + this.async('d1')(null, 'a');
  88 + },
  89 + function() {
  90 + this.async('d2')(null, 'b');
  91 + },
  92 + function() {
  93 + var str = this.data.d1 + this.data.d2;
  94 + assert.equal(str, 'ab');
  95 + }
  96 + )(done);
  97 + });
  98 +
  99 + it('no exception', function(done) {
  100 + block(
  101 + function() {
  102 + this.data.d1 = 'a';
  103 + },
  104 + function() {
  105 + this.data.d1 += 'b';
  106 + },
  107 + function cat(err) {
  108 + this.data.d1 += 'c';
  109 + },
  110 + function fin() {
  111 + assert.equal(this.data.d1, 'ab');
  112 + }
  113 + )(done);
  114 + });
  115 +
  116 + it('throw exception', function(done) {
  117 + block(
  118 + function() {
  119 + throw new Error('test');
  120 + },
  121 + function() {
  122 + assert(false);
  123 + },
  124 + function cat(err) {
  125 + assert.equal(err.message, 'test');
  126 + }
  127 + )(done);
  128 + });
  129 +
  130 + it('throw exception from async', function(done) {
  131 + block(
  132 + function() {
  133 + fs.readFile('not_found', 'utf8', this.async('d1'));
  134 + },
  135 + function() {
  136 + assert(false, 'assert');
  137 + },
  138 + function cat(err) {
  139 + assert.notEqual(err.message, 'assert');
  140 + assert.notEqual(err, null);
  141 + }
  142 + )(done);
  143 + });
  144 +
  145 + it('throw exception from async parallel', function(done) {
  146 + block(
  147 + function() {
  148 + fs.readFile('not_found', 'utf8', this.async('d1'));
  149 + fs.readFile('not_found', 'utf8', this.async('d2'));
  150 + fs.readFile('not_found', 'utf8', this.async('d3'));
  151 + },
  152 + function() {
  153 + assert(false, 'assert');
  154 + },
  155 + function cat(err) {
  156 + assert.notEqual(err.message, 'assert');
  157 + assert.notEqual(err, null);
  158 + }
  159 + )(done);
  160 + });
  161 +
  162 + it('throw exception from inner block', function(done) {
  163 + block(
  164 + function() {
  165 + block(
  166 + function() {
  167 + ;
  168 + },
  169 + function() {
  170 + throw new Error('test');
  171 + },
  172 + function() {
  173 + assert(false);
  174 + }
  175 + )(this.async());
  176 + },
  177 + function() {
  178 + assert(false);
  179 + },
  180 + function cat(err) {
  181 + assert.equal(err.message, 'test');
  182 + }
  183 + )(done);
  184 + });
  185 +
  186 + it('catch exception and throw', function(done) {
  187 + block(
  188 + function() {
  189 + throw new Error('test');
  190 + },
  191 + function() {
  192 + assert(false);
  193 + },
  194 + function cat(err) {
  195 + assert.notEqual(err, null);
  196 + assert.equal(err.message, 'test');
  197 + throw err;
  198 + },
  199 + function fin(err) {
  200 + assert.notEqual(err, null);
  201 + assert.equal(err.message, 'test');
  202 + }
  203 + )(function(err) {
  204 + assert.notEqual(err, null);
  205 + done((err.message == 'test') ? null : err);
  206 + });
  207 + });
  208 +
  209 + it('no catch exception', function(done) {
  210 + block(
  211 + function() {
  212 + throw new Error('test');
  213 + },
  214 + function() {
  215 + assert(false);
  216 + }
  217 + )(function(err) {
  218 + assert.notEqual(err, null);
  219 + done((err.message == 'test') ? null : err);
  220 + });
  221 + });
  222 +
  223 + it('no catch exception and fin', function(done) {
  224 + block(
  225 + function() {
  226 + throw new Error('test');
  227 + },
  228 + function() {
  229 + assert(false);
  230 + },
  231 + function fin(err) {
  232 + assert.notEqual(err, null);
  233 + assert.equal(err.message, 'test');
  234 + }
  235 + )(function(err) {
  236 + assert.notEqual(err, null);
  237 + done((err.message == 'test') ? null : err);
  238 + });
  239 + });
  240 +
  241 + it('no catch exception and fin async', function(done) {
  242 + block(
  243 + function() {
  244 + throw new Error('test');
  245 + },
  246 + function() {
  247 + assert(false);
  248 + },
  249 + function fin(err) {
  250 + assert.notEqual(err, null);
  251 + assert.equal(err.message, 'test');
  252 + fs.readFile(fileA, 'utf8', this.async('d1'));
  253 + fs.readFile(fileB, 'utf8', this.async('d2'));
  254 + }
  255 + )(function(err) {
  256 + var str = this.data.d1 + this.data.d2;
  257 + assert.equal(str, 'aaaabbbb');
  258 + assert.notEqual(err, null);
  259 + done((err.message == 'test') ? null : err);
  260 + });
  261 + });
  262 +
  263 + it('jump to end', function(done) {
  264 + block(
  265 + function() {
  266 + this.data.d1 = 'a';
  267 + if (true) {
  268 + return this.end();
  269 + }
  270 + assert(false);
  271 + },
  272 + function() {
  273 + assert(false);
  274 + },
  275 + function cat(err) {
  276 + assert.notEqual(err, null);
  277 + throw err;
  278 + },
  279 + function fin(err) {
  280 + assert.equal(err, null);
  281 + this.data.d1 += 'b';
  282 + }
  283 + )(function(err) {
  284 + assert.equal(this.data.d1, 'ab');
  285 + done(err);
  286 + });
  287 + });
  288 +
  289 + it('nested block', function(done) {
  290 + block(
  291 + function() {
  292 + fs.readFile(fileA, 'utf8', this.async('d1'));
  293 + fs.readFile(fileB, 'utf8', this.async('d2'));
  294 + },
  295 + function() {
  296 + fs.readFile(fileA, 'utf8', this.async('d3'));
  297 + block(
  298 + function() {
  299 + fs.readFile(fileB, 'utf8', this.async('e1'));
  300 + fs.readFile(fileA, 'utf8', this.async('e2'));
  301 + },
  302 + function() {
  303 + fs.readFile(fileB, 'utf8', this.async('e3'));
  304 + }
  305 + )(this.async('d4'));
  306 + },
  307 + function() {
  308 + var str = this.data.d1 + this.data.d2 + this.data.d3 +
  309 + this.data.d4.e1 + this.data.d4.e2 + this.data.d4.e3;
  310 + assert.equal(str, 'aaaabbbbaaaabbbbaaaabbbb');
  311 + }
  312 + )(done);
  313 + });
  314 +
  315 + it('nested and parallel block', function(done) {
  316 + block(
  317 + function() {
  318 + fs.readFile(fileA, 'utf8', this.async('d1'));
  319 + fs.readFile(fileB, 'utf8', this.async('d2'));
  320 + },
  321 + function() {
  322 + fs.readFile(fileA, 'utf8', this.async('d3'));
  323 + block(
  324 + function() {
  325 + fs.readFile(fileB, 'utf8', this.async('e1'));
  326 + fs.readFile(fileA, 'utf8', this.async('e2'));
  327 + },
  328 + function() {
  329 + fs.readFile(fileB, 'utf8', this.async('e3'));
  330 + }
  331 + )(this.async('d4'));
  332 + block(
  333 + function() {
  334 + fs.readFile(fileA, 'utf8', this.async('e1'));
  335 + },
  336 + function() {
  337 + fs.readFile(fileB, 'utf8', this.async('e2'));
  338 + fs.readFile(fileA, 'utf8', this.async('e3'));
  339 + }
  340 + )(this.async('d5'));
  341 + },
  342 + function() {
  343 + var str = this.data.d1 + this.data.d2 + this.data.d3 +
  344 + this.data.d4.e1 + this.data.d4.e2 + this.data.d4.e3 +
  345 + this.data.d5.e1 + this.data.d5.e2 + this.data.d5.e3;
  346 + assert.equal(str, 'aaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaa');
  347 + }
  348 + )(done);
  349 + });
  350 +
  351 + it('bench', function(done) {
  352 + var loopCount = 10000;
  353 + (function serialLoop() {
  354 + if (loopCount-- <= 0) {
  355 + done();
  356 + return;
  357 + }
  358 + block(
  359 + function() {
  360 + process.nextTick(this.async());
  361 + },
  362 + function() {
  363 + process.nextTick(this.async());
  364 + process.nextTick(this.async());
  365 + },
  366 + function() {
  367 + process.nextTick(this.async());
  368 + process.nextTick(this.async());
  369 + process.nextTick(this.async());
  370 + },
  371 + function() {
  372 + process.nextTick(this.async());
  373 + process.nextTick(this.async());
  374 + process.nextTick(this.async());
  375 + process.nextTick(this.async());
  376 + }
  377 + )(serialLoop);
  378 + })();
  379 + });
  380 +
  381 +});
1  test/rsc/_a.txt
... ... @@ -0,0 +1 @@
  1 +aaaa
1  test/rsc/_b.txt
... ... @@ -0,0 +1 @@
  1 +bbbb

0 comments on commit 95d2c53

Please sign in to comment.
Something went wrong with that request. Please try again.