├── .gitignore ├── .jshintrc ├── .travis.yml ├── CHANGES.md ├── LICENSE ├── README.md ├── index.js ├── package.json └── test └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "indent": 2, 3 | "latedef": "nofunc", 4 | "noempty": true, 5 | "quotmark": true, 6 | "undef": true, 7 | "unused": true, 8 | "lastsemic": true, 9 | 10 | "esnext": true, 11 | "noyield": true, 12 | 13 | "devel": true, 14 | "node": true, 15 | 16 | "globals": { 17 | "describe": false, 18 | "it": false, 19 | "xit": false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.12" 4 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | Changes 2 | ======= 3 | 4 | 0.1.4 5 | ----- 6 | 7 | - fix error when yielding null 8 | - readme updates 9 | 10 | 0.1.3 11 | ----- 12 | 13 | - doc updates 14 | - cleanup 15 | 16 | 0.1.2 17 | ----- 18 | 19 | - added tests 20 | - added docs 21 | - minor cleanup 22 | 23 | 0.1.0 24 | ----- 25 | 26 | - initial commit 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Storehouse 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/storehouse/engen.svg?branch=master)](http://travis-ci.org/storehouse/engen) 2 | 3 | engen 4 | ===== 5 | 6 | > The generator engine. 7 | 8 | Async control flow using pure ES6 generators. 9 | 10 | Requires ES6 generator support. Tested on Node 0.12 with the `--harmony` flag. 11 | 12 | Async code based on `yield` 13 | --------------------------- 14 | 15 | With generators, calling an asynchronous function and waiting for its result is 16 | as simple as calling `yield f()`. Parameters and return values work exactly as if 17 | the code were synchronous. 18 | 19 | ```javascript 20 | var g = require('engen'); 21 | 22 | function *c(x) { 23 | yield g.wait(1000); 24 | return x; 25 | } 26 | 27 | function *b() { 28 | return yield c('b'); // takes 1000ms 29 | } 30 | 31 | function *a() { 32 | var b = yield b(); 33 | return 'a' + b; 34 | } 35 | 36 | g.run(a(), function(err, res) { 37 | console.log(err); // null 38 | console.log(res); // 'ab' 39 | }); 40 | ``` 41 | 42 | Yielding an array of generators will run them in parallel and return an array 43 | of ordered results. 44 | 45 | ```javascript 46 | var g = require('engen'); 47 | 48 | function *a() { 49 | yield g.wait(1000); 50 | return 'a'; 51 | } 52 | 53 | function *b() { 54 | yield g.wait(2000); 55 | return 'b'; 56 | } 57 | 58 | function *f() { 59 | return yield [a(), b()]; // takes 2000ms 60 | } 61 | 62 | g.run(f(), function(err, res) { 63 | console.log(err); // null 64 | console.log(res); // ['a', 'b'] 65 | }); 66 | ``` 67 | 68 | Yielding an object of generators behaves identically to yielding an array, but 69 | the resulting value is an object. 70 | 71 | ```javascript 72 | var g = require('engen'); 73 | 74 | function *a() { 75 | yield g.wait(1000); 76 | return 'a'; 77 | } 78 | 79 | function *b() { 80 | yield g.wait(2000); 81 | return 'b'; 82 | } 83 | 84 | function *f() { 85 | return yield {a: a(), b: b()}; // takes 2000ms 86 | } 87 | 88 | g.run(f(), function(err, res) { 89 | console.log(err); // null 90 | console.log(res); // {a: 'a', b: 'b'} 91 | }); 92 | ``` 93 | 94 | Inside collections, mixing generators and simple values is allowed. 95 | Generators will be waited for, anything else will be passed through untouched. 96 | 97 | **Note**: this includes objects and arrays: be careful not to nest collections of generators. 98 | 99 | ```javascript 100 | var g = require('engen'); 101 | 102 | function *a() { 103 | yield g.wait(1000); 104 | return 'a'; 105 | } 106 | 107 | function *f() { 108 | return yield [a(), false, null, {}]; 109 | } 110 | 111 | g.run(f(), function(err, res) { 112 | console.log(err); // null 113 | console.log(res); // ['a', false, null, {}]; 114 | }); 115 | ``` 116 | 117 | Exceptions also behave as if the code were synchronous: 118 | 119 | ```javascript 120 | var g = require('engen'); 121 | 122 | function *b() { 123 | yield g.wait(1000); 124 | throw Error('oops'); 125 | } 126 | 127 | function *a() { 128 | try { 129 | yield b(); // takes 1000ms 130 | } catch(err) { 131 | console.log(err) // 'Error: oops' 132 | } 133 | return 'a'; 134 | } 135 | 136 | g.run(a(), function(err, res) { 137 | console.log(err); // null 138 | }); 139 | 140 | g.run(b(), function(err, res) { 141 | console.log(err); // 'Error: oops' 142 | }); 143 | ``` 144 | 145 | 146 | API 147 | --- 148 | 149 | ### engen.run() 150 | 151 | Run generator based code from callback-land. 152 | 153 | ```javascript 154 | var g = require('engen'); 155 | 156 | function *f() { 157 | yield g.wait(2000); 158 | return 'done'; 159 | } 160 | 161 | g.run(f(), function(err, result) { 162 | console.log(err); // null 163 | console.log(result); // 'done' 164 | }); 165 | 166 | ``` 167 | 168 | ### engen.wrap() 169 | 170 | Wrap callback-style code and call it from generator-land. 171 | 172 | ```javascript 173 | var g = require('engen'); 174 | var readFile = g.wrap(require('fs').readFile); 175 | 176 | function *f() { 177 | yield g.wait(1000); 178 | return yield readFile('test.js'); 179 | } 180 | 181 | g.run(f(), function(err, res) { 182 | console.log(err); // null 183 | console.log(res); // 184 | }); 185 | ``` 186 | 187 | By default, `wrap()` expects callbacks to have the standard Node signature: 188 | 189 | ```javascript 190 | function(error, result) {} 191 | ``` 192 | 193 | To wrap an unusual callback, you can pass in a function as a second parameter 194 | to `wrap()` to perform custom handling of the callback parameters. 195 | 196 | To handle multiple return values and return them as an array: 197 | 198 | ```javascript 199 | var g = require('engen'); 200 | 201 | var f = g.wrap(function(cb) { 202 | cb(err, 12, 34, 56); 203 | }, function(err, a, b, c) { 204 | if (err) throw err; 205 | return [a, b, c]; 206 | }); 207 | ``` 208 | 209 | To handle callbacks without an `err` parameter: 210 | 211 | ```javascript 212 | var g = require('engen'); 213 | 214 | var f = g.wrap(function(cb) { 215 | cb(12, 34, 56); 216 | }, function(a, b, c) { 217 | return [a, b, c]; 218 | }); 219 | ``` 220 | 221 | For your convenience, these two handlers are built in, as `g.multipleReturnCallback` and `g.noErrorCallback`. 222 | 223 | ### engen.wait() 224 | 225 | Generator-based version of `setTimeout`, provided for convenience. 226 | 227 | ```javascript 228 | var g = require('engen'); 229 | 230 | function *f() { 231 | yield g.wait(1000); // pauses for 1000ms 232 | return 'done'; 233 | } 234 | 235 | g.run(f()); 236 | ``` 237 | 238 | License 239 | ------- 240 | 241 | MIT 242 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | function ResumeHandler() { 2 | this.__engen_resumehandler__ = true; 3 | } 4 | 5 | function MultipleErrors(errors) { 6 | this.name = 'MultipleErrors'; 7 | this.message = 'engen: parallel yield got multiple errors:\n' + errors.map(function(err) { 8 | var message = err instanceof Error ? err.message : JSON.stringify(err); 9 | return message + '\n\n' + err.stack; 10 | }).join('\n'); 11 | } 12 | 13 | MultipleErrors.prototype = Object.create(Error.prototype); 14 | MultipleErrors.prototype.constructor = MultipleErrors; 15 | 16 | function isGeneratorProto(v) { 17 | return v+'' == '[object Generator]'; 18 | } 19 | 20 | function parallel(object, loop, callback) { 21 | var keys = Object.keys(object); 22 | var outstanding = keys.length; 23 | for (var i = 0, l = keys.length; i < l; i++) { 24 | loop(keys[i], object[keys[i]], done); 25 | } 26 | function done() { 27 | outstanding--; 28 | if (outstanding === 0) callback(); 29 | } 30 | } 31 | 32 | function step(iterator, error, returnValues, next) { 33 | var iteration; 34 | 35 | try { 36 | if (error) { 37 | iteration = iterator.throw.call(iterator, error); 38 | } else { 39 | iteration = iterator.next.apply(iterator, returnValues); 40 | } 41 | } catch (err) { 42 | return next(err); 43 | } 44 | 45 | var value = iteration.value; 46 | 47 | if (iteration.done) return next && next(null, value); 48 | 49 | if (value && (value.__engen_resumehandler__ || value instanceof ResumeHandler)) { 50 | value.resume = function(error) { 51 | setTimeout(step.bind(null, iterator, error, Array.prototype.slice.call(arguments, 1), next), 0); 52 | }; 53 | iterator.next(); 54 | } else if (isGeneratorProto(value)) { 55 | step(value, null, [], function(error) { 56 | step(iterator, error, Array.prototype.slice.call(arguments, 1), next); 57 | }); 58 | } else if (value !== null && typeof value == 'object') { 59 | var results = Array.isArray(value) ? [] : {}; 60 | if (!Object.keys(value).length) { 61 | step(iterator, null, [results], next); 62 | return; 63 | } 64 | var errors = []; 65 | parallel(value, function(key, val, done) { 66 | if (!isGeneratorProto(val)) { 67 | results[key] = val; 68 | done(); 69 | } else { 70 | step(val, null, [], function(error, result) { 71 | if (error) { 72 | errors.push(error); 73 | } else { 74 | results[key] = result; 75 | } 76 | done(); 77 | }); 78 | } 79 | }, function() { 80 | if (errors.length === 1) { 81 | step(iterator, errors[0], [], next); 82 | } else if (errors.length > 1) { 83 | step(iterator, new MultipleErrors(errors), [], next); 84 | } else { 85 | step(iterator, null, [results], next); 86 | } 87 | }); 88 | } else { 89 | throw new Error('engen: yielded something other than a generator, an array of generators, or a ResumeHandler: ' + value); 90 | } 91 | } 92 | 93 | var engen = {}; 94 | 95 | engen.run = function(generator, callback) { 96 | if (typeof generator === 'function') { 97 | generator = generator(); 98 | } 99 | if (!isGeneratorProto(generator)) { 100 | throw new Error('engen: first argument to run() must be a generator or a generator function, got: ' + generator); 101 | } 102 | step(generator, null, [], callback); 103 | }; 104 | 105 | engen.wrap = function(f, callbackWrapper) { 106 | return function* wrapper() { 107 | var args = Array.prototype.slice.call(arguments); 108 | var resumeHandler = new ResumeHandler(); 109 | args.push(function() { 110 | if (!resumeHandler.resume) { 111 | throw new Error('engen: wrapped function returned before its resumeHandler was ready: ' + f); 112 | } 113 | try { 114 | resumeHandler.resume.apply(null, callbackWrapper ? [null, callbackWrapper.apply(this, arguments)] : arguments); 115 | } catch(err) { 116 | resumeHandler.resume.call(null, err); 117 | } 118 | }); 119 | yield resumeHandler; 120 | f.apply(this, args); 121 | return yield null; 122 | }; 123 | }; 124 | 125 | engen.multipleReturnCallback = function(err) { 126 | if (err) throw err; 127 | return Array.prototype.slice.call(arguments, 1); 128 | }; 129 | 130 | engen.noErrorCallback = function(value) { 131 | return value; 132 | }; 133 | 134 | engen.multipleReturnNoErrorCallback = function() { 135 | return Array.prototype.slice.call(arguments); 136 | }; 137 | 138 | engen.wait = engen.wrap(function(interval, cb) { 139 | setTimeout(function() { 140 | cb(); 141 | }, interval); 142 | }); 143 | 144 | module.exports = engen; 145 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "engen", 3 | "version": "0.1.8", 4 | "description": "Async control flow using pure ES6 generators.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha --harmony", 8 | "lint": "jshint index.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git@github.com:storehouse/engen.git" 13 | }, 14 | "keywords": [ 15 | "generators", 16 | "async", 17 | "flow" 18 | ], 19 | "author": "Storehouse", 20 | "contributors": [ 21 | { 22 | "name": "Stefano Attardi", 23 | "email": "stefano@attardi.org" 24 | }, 25 | { 26 | "name": "Artem Nezvigin", 27 | "email": "artem@artnez.com" 28 | } 29 | ], 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/storehouse/engen/issues" 33 | }, 34 | "homepage": "https://github.com/storehouse/engen", 35 | "devDependencies": { 36 | "jshint": "2.6.0", 37 | "mocha": "2.1.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var engen = require('../index'); 2 | var assert = require('assert'); 3 | 4 | describe('engen.run()', function() { 5 | 6 | describe('basics', function() { 7 | 8 | it('should pass down arguments correctly', function(done) { 9 | function *b() { 10 | assert.deepEqual([].slice.apply(arguments), [12, 34, 56]); 11 | } 12 | 13 | function *a() { 14 | yield b(12, 34, 56); 15 | } 16 | 17 | engen.run(a(), done); 18 | }); 19 | 20 | it('should pass return values back correctly', function(done) { 21 | function *b() { 22 | return 12; 23 | } 24 | 25 | function *a() { 26 | var x = yield b(); 27 | assert.equal(x, 12); 28 | } 29 | 30 | engen.run(a(), done); 31 | }); 32 | 33 | it('should pass return values all the way to the top', function() { 34 | function *b() { 35 | return 12; 36 | } 37 | 38 | function *a() { 39 | return yield b(); 40 | } 41 | 42 | engen.run(a(), function(err, val) { 43 | assert.equal(val, 12); 44 | }); 45 | }); 46 | 47 | it('should allow calling without a callback', function() { 48 | function *b() { 49 | return 12; 50 | } 51 | 52 | function *a() { 53 | return yield b(); 54 | } 55 | 56 | engen.run(a()); 57 | }); 58 | 59 | it('should allow calling with a function as first parameter', function() { 60 | function *b() { 61 | return 12; 62 | } 63 | 64 | function *a() { 65 | return yield b(); 66 | } 67 | 68 | engen.run(a, function(err, value) { 69 | assert.equal(value, 12); 70 | }); 71 | }); 72 | }); 73 | 74 | describe('parallel execution', function() { 75 | 76 | it('should execute arrays of generators in parallel', function(done) { 77 | this.timeout(40); 78 | 79 | function *c() { 80 | yield engen.wait(30); 81 | return 24; 82 | } 83 | 84 | function *b() { 85 | yield engen.wait(20); 86 | return 12; 87 | } 88 | 89 | function *a() { 90 | var res = yield [b(), c()]; 91 | assert.equal(res[0], 12); 92 | assert.equal(res[1], 24); 93 | } 94 | 95 | engen.run(a(), done); 96 | }); 97 | 98 | it('should execute objects of generators in parallel', function(done) { 99 | this.timeout(40); 100 | 101 | function *c() { 102 | yield engen.wait(30); 103 | return 24; 104 | } 105 | 106 | function *b() { 107 | yield engen.wait(20); 108 | return 12; 109 | } 110 | 111 | function *a() { 112 | var res = yield {b: b(), c: c()}; 113 | assert.equal(res.b, 12); 114 | assert.equal(res.c, 24); 115 | } 116 | 117 | engen.run(a(), done); 118 | }); 119 | 120 | 121 | it('should allow literal values in arrays', function(done) { 122 | function *b() { 123 | return 12; 124 | } 125 | 126 | function *a() { 127 | var res = yield [b(), 456, null]; 128 | assert.deepEqual(res, [12, 456, null]); 129 | } 130 | 131 | engen.run(a(), done); 132 | }); 133 | }); 134 | 135 | describe('exception handling', function() { 136 | 137 | it('should bubble up exceptions to the callback', function() { 138 | function *b() { 139 | throw new Error('crap'); 140 | } 141 | 142 | function *a() { 143 | yield b(); 144 | } 145 | 146 | engen.run(a(), function(err) { 147 | assert(err); 148 | }); 149 | }); 150 | 151 | it('should allow catching exceptions', function() { 152 | function *b() { 153 | throw new Error('crap'); 154 | } 155 | 156 | function *a() { 157 | try { 158 | yield b(); 159 | } catch (err) { 160 | assert(err); 161 | } 162 | } 163 | 164 | engen.run(a(), function(err) { 165 | assert.ifError(err); 166 | }); 167 | }); 168 | 169 | it('should propagate single exceptions through parallel execution', function() { 170 | function *c() { 171 | return 12; 172 | } 173 | 174 | function *b() { 175 | throw new Error('crap'); 176 | } 177 | 178 | function *a() { 179 | yield [b(), c()]; 180 | } 181 | 182 | engen.run(a(), function(err) { 183 | assert(err); 184 | assert.equal(err.message, 'crap'); 185 | }); 186 | }); 187 | 188 | it('should wrap multiple parallel exceptions', function() { 189 | function *c() { 190 | throw new Error('c'); 191 | } 192 | 193 | function *b() { 194 | throw new Error('b'); 195 | } 196 | 197 | function *a() { 198 | yield [b(), c()]; 199 | } 200 | 201 | engen.run(a(), function(err) { 202 | assert(err); 203 | assert(/multiple errors/.test(err.message), err.message); 204 | assert(/Error: b/.test(err.message)); 205 | assert(/Error: c/.test(err.message)); 206 | }); 207 | }); 208 | 209 | }); 210 | 211 | describe('helpful usage error messages', function() { 212 | 213 | it('should throw when calling run() with unexpected first argument', function() { 214 | assert.throws( 215 | function() { 216 | engen.run(123); 217 | }, 218 | /argument to run\(\) must be/ 219 | ); 220 | }); 221 | 222 | it('should throw when yielding non-generators', function() { 223 | 224 | function *a() { 225 | yield 123; 226 | } 227 | 228 | assert.throws( 229 | function() { 230 | engen.run(a()); 231 | }, 232 | /yielded something other than/ 233 | ); 234 | 235 | function *a() { 236 | yield null; 237 | } 238 | 239 | assert.throws( 240 | function() { 241 | engen.run(a()); 242 | }, 243 | /yielded something other than/ 244 | ); 245 | 246 | }); 247 | 248 | xit('should throw when yielding a nested array of generators', function(done) { 249 | function *c() { 250 | return 23; 251 | } 252 | 253 | function *b() { 254 | return 12; 255 | } 256 | 257 | function *a() { 258 | yield [b(), [c()]]; 259 | } 260 | 261 | assert.throws( 262 | function() { 263 | engen.run(a(), done); 264 | }, 265 | /do not nest/ 266 | ); 267 | }); 268 | 269 | }); 270 | 271 | }); 272 | 273 | 274 | describe('engen.wrap()', function() { 275 | 276 | describe('basics', function() { 277 | 278 | it('should allow wrapping a simple callback-based function', function(done) { 279 | 280 | var wait = engen.wrap(function(time, cb) { 281 | return setTimeout(cb, time); 282 | }); 283 | 284 | var finished = false; 285 | 286 | function *a() { 287 | yield wait(20); 288 | finished = true; 289 | } 290 | 291 | engen.run(a(), function() { 292 | assert.equal(finished, true); 293 | done(); 294 | }); 295 | 296 | assert.equal(finished, false); 297 | 298 | }); 299 | 300 | it('should support functions that finish synchronously', function(done) { 301 | 302 | var instant = engen.wrap(function(x, cb) { 303 | cb(null, x); 304 | }); 305 | 306 | function *a() { 307 | var x = yield instant(20); 308 | return x; 309 | } 310 | 311 | engen.run(a(), function(err, x) { 312 | assert.equal(x, 20); 313 | done(); 314 | }); 315 | 316 | }); 317 | 318 | it('should return first value from callbacks with multiple return values', function(done) { 319 | var b = engen.wrap(function(cb) { 320 | cb(null, 12, 34, 56); 321 | }); 322 | 323 | function *a() { 324 | var res = yield b(); 325 | assert.equal(res, 12); 326 | return res; 327 | } 328 | 329 | engen.run(a(), function(err, res) { 330 | assert.ifError(err); 331 | assert.equal(res, 12); 332 | done(); 333 | }); 334 | }); 335 | 336 | it('should convert errors into exceptions', function() { 337 | 338 | var b = engen.wrap(function(cb) { 339 | cb(new Error('oops')); 340 | }); 341 | 342 | function *a() { 343 | yield b(); 344 | } 345 | 346 | engen.run(a(), function(err) { 347 | assert(err); 348 | assert(/oops/.test(err.message)); 349 | }); 350 | 351 | }); 352 | 353 | }); 354 | 355 | describe('callback wrapper', function() { 356 | 357 | it('should allow wrapping callbacks with multiple return values', function(done) { 358 | 359 | var b = engen.wrap(function(cb) { 360 | cb(null, 12, 34, 56); 361 | }, function(err, a, b, c) { 362 | if (err) throw err; 363 | return [a, b, c]; 364 | }); 365 | 366 | function *a() { 367 | var res = yield b(); 368 | assert.deepEqual(res, [12, 34, 56]); 369 | return res; 370 | } 371 | 372 | engen.run(a(), function(err, res) { 373 | assert.ifError(err); 374 | assert.deepEqual(res, [12, 34, 56]); 375 | done(); 376 | }); 377 | 378 | }); 379 | 380 | it('should allow wrapping callbacks without an error parameter', function(done) { 381 | 382 | var b = engen.wrap(function(cb) { 383 | cb(12, 34, 56); 384 | }, function(a, b, c) { 385 | return [a, b, c]; 386 | }); 387 | 388 | function *a() { 389 | var res = yield b(); 390 | assert.deepEqual(res, [12, 34, 56]); 391 | return res; 392 | } 393 | 394 | engen.run(a(), function(err, res) { 395 | assert.ifError(err); 396 | assert.deepEqual(res, [12, 34, 56]); 397 | done(); 398 | }); 399 | 400 | }); 401 | 402 | it('should allow wrapping callbacks with multiple return values using multipleReturnCallback', function(done) { 403 | 404 | var b = engen.wrap(function(cb) { 405 | cb(null, 12, 34, 56); 406 | }, engen.multipleReturnCallback); 407 | 408 | function *a() { 409 | var res = yield b(); 410 | assert.deepEqual(res, [12, 34, 56]); 411 | return res; 412 | } 413 | 414 | engen.run(a(), function(err, res) { 415 | assert.ifError(err); 416 | assert.deepEqual(res, [12, 34, 56]); 417 | done(); 418 | }); 419 | 420 | }); 421 | 422 | it('should allow wrapping callbacks without an error parameter using noErrorCallback', function(done) { 423 | 424 | var b = engen.wrap(function(cb) { 425 | cb(12); 426 | }, engen.noErrorCallback); 427 | 428 | function *a() { 429 | var res = yield b(); 430 | assert.deepEqual(res, 12); 431 | return res; 432 | } 433 | 434 | engen.run(a(), function(err, res) { 435 | assert.ifError(err); 436 | assert.deepEqual(res, 12); 437 | done(); 438 | }); 439 | 440 | }); 441 | 442 | it('should allow wrapping callbacks with multiple return values an error parameter using multipleReturnNoErrorCallback', function(done) { 443 | 444 | var b = engen.wrap(function(cb) { 445 | cb(12, 34, 56); 446 | }, engen.multipleReturnNoErrorCallback); 447 | 448 | function *a() { 449 | var res = yield b(); 450 | assert.deepEqual(res, [12, 34, 56]); 451 | return res; 452 | } 453 | 454 | engen.run(a(), function(err, res) { 455 | assert.ifError(err); 456 | assert.deepEqual(res, [12, 34, 56]); 457 | done(); 458 | }); 459 | 460 | }); 461 | 462 | it('should handle exceptions from wrapped callbacks', function(done) { 463 | 464 | var b = engen.wrap(function(cb) { 465 | cb(new Error('oops'), 12, 34, 56); 466 | }, function(err, a, b, c) { 467 | if (err) throw err; 468 | return [a, b, c]; 469 | }); 470 | 471 | function *a() { 472 | var res; 473 | try { 474 | res = yield b(); 475 | } catch(err) { 476 | assert(err); 477 | assert(/oops/.test(err.message)); 478 | throw err; 479 | } 480 | return res; 481 | } 482 | 483 | engen.run(a(), function(err/*, res*/) { 484 | assert(err); 485 | assert(/oops/.test(err.message)); 486 | done(); 487 | }); 488 | 489 | }); 490 | }); 491 | 492 | }); 493 | --------------------------------------------------------------------------------