├── .gitignore ├── LICENSE ├── README.md ├── benchmarks └── index.js ├── examples ├── async.js ├── blocking.js ├── exceptions.js ├── express.js ├── fiber.js ├── future.js ├── object.js ├── repl.js ├── scope.js ├── simple.js ├── simple2.js └── vars.js ├── lib └── sync.js ├── package.json └── test ├── async.js ├── fiber.js ├── future.js ├── index.js ├── sleep.js └── sync.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Yuriy Bogdanov 2 | 3 | (The MIT License) 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 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecation Notice 2 | 3 | As of this notice, the node-sync project has been archived and will no longer receive maintenance or updates. I want to express my gratitude to the entire community for your usage and contributions. 4 | 5 | # Introduction 6 | node-sync is a simple library that allows you to call any asynchronous function in synchronous way. The main benefit is that it uses javascript-native design - Function.prototype.sync function, instead of heavy APIs which you'll need to learn. Also, asynchronous function which was called synchronously through node-sync doesn't blocks the whole process - it blocks only current thread! 7 | 8 | It built on [node-fibers](https://github.com/laverdet/node-fibers) library as a multithreading solution. 9 | 10 | # Examples 11 | Simply call asynchronous function synchronously: 12 | 13 | ```javascript 14 | var Sync = require('sync'); 15 | 16 | function asyncFunction(a, b, callback) { 17 | process.nextTick(function(){ 18 | callback(null, a + b); 19 | }) 20 | } 21 | 22 | // Run in a fiber 23 | Sync(function(){ 24 | 25 | // Function.prototype.sync() interface is same as Function.prototype.call() - first argument is 'this' context 26 | var result = asyncFunction.sync(null, 2, 3); 27 | console.log(result); // 5 28 | 29 | // Read file synchronously without blocking whole process? no problem 30 | var source = require('fs').readFile.sync(null, __filename); 31 | console.log(String(source)); // prints the source of this example itself 32 | }) 33 | ``` 34 | 35 | It throws exceptions! 36 | 37 | ```javascript 38 | var Sync = require('sync'); 39 | 40 | function asyncFunction(a, b, callback) { 41 | process.nextTick(function(){ 42 | callback('something went wrong'); 43 | }) 44 | } 45 | 46 | // Run in a fiber 47 | Sync(function(){ 48 | 49 | try { 50 | var result = asyncFunction.sync(null, 2, 3); 51 | } 52 | catch (e) { 53 | console.error(e); // something went wrong 54 | } 55 | }) 56 | 57 | // Or simply specify callback function for Sync fiber 58 | // handy when you use Sync in asynchronous environment 59 | Sync(function(){ 60 | 61 | // The result will be passed to a Sync callback 62 | var result = asyncFunction.sync(null, 2, 3); 63 | return result; 64 | 65 | }, function(err, result){ // <-- standard callback 66 | 67 | if (err) console.error(err); // something went wrong 68 | 69 | // The result which was returned from Sync body function 70 | console.log(result); 71 | }) 72 | ``` 73 | 74 | Transparent integration 75 | 76 | ```javascript 77 | var Sync = require('sync'); 78 | 79 | var MyNewFunctionThatUsesFibers = function(a, b) { // <-- no callback here 80 | 81 | // we can use yield here 82 | // yield(); 83 | 84 | // or throw an exception! 85 | // throw new Error('something went wrong'); 86 | 87 | // or even sleep 88 | // Sync.sleep(200); 89 | 90 | // or turn fs.readFile to non-blocking synchronous function 91 | // var source = require('fs').readFile.sync(null, __filename) 92 | 93 | return a + b; // just return a value 94 | 95 | }.async() // <-- here we make this function friendly with async environment 96 | 97 | // Classic asynchronous nodejs environment 98 | var MyOldFashoinAppFunction = function() { 99 | 100 | // We just use our MyNewFunctionThatUsesFibers normally, in a callback-driven way 101 | MyNewFunctionThatUsesFibers(2, 3, function(err, result){ 102 | 103 | // If MyNewFunctionThatUsesFibers will throw an exception, it will go here 104 | if (err) return console.error(err); 105 | 106 | // 'return' value of MyNewFunctionThatUsesFibers 107 | console.log(result); // 5 108 | }) 109 | } 110 | 111 | // From fiber environment 112 | Sync(function(){ 113 | 114 | // Run MyNewFunctionThatUsesFibers synchronously 115 | var result = MyNewFunctionThatUsesFibers(2, 3); 116 | console.log(result); // 5 117 | 118 | // Or use sync() for it (same behavior) 119 | var result = MyNewFunctionThatUsesFibers.sync(null, 2, 3); 120 | console.log(result); // 5 121 | }) 122 | ``` 123 | 124 | Parallel execution: 125 | 126 | ```javascript 127 | var Sync = require('sync'), 128 | Future = Sync.Future(); 129 | 130 | // Run in a fiber 131 | Sync(function(){ 132 | try { 133 | // Three function calls in parallel 134 | var foo = asyncFunction.future(null, 2, 3); 135 | var bar = asyncFunction.future(null, 5, 5); 136 | var baz = asyncFunction.future(null, 10, 10); 137 | 138 | // We are immediately here, no blocking 139 | 140 | // foo, bar, baz - our tickets to the future! 141 | console.log(foo); // { [Function: Future] result: [Getter], error: [Getter] } 142 | 143 | // Get the results 144 | // (when you touch 'result' getter, it blocks until result would be returned) 145 | console.log(foo.result, bar.result, baz.result); // 5 10 20 146 | 147 | // Or you can straightly use Sync.Future without wrapper 148 | // This call doesn't blocks 149 | asyncFunction(2, 3, foo = Future()); 150 | 151 | // foo is a ticket 152 | console.log(foo); // { [Function: Future] result: [Getter], error: [Getter] } 153 | 154 | // Wait for the result 155 | console.log(foo.result); // 5 156 | } 157 | catch (e) { 158 | // If some of async functions returned an error to a callback 159 | // it will be thrown as exception 160 | console.error(e); 161 | } 162 | }) 163 | ``` 164 | 165 | Timeouts support 166 | 167 | ```javascript 168 | var Sync = require('sync'), 169 | Future = Sync.Future; 170 | 171 | function asyncFunction(a, b, callback) { 172 | setTimeout(function(){ 173 | callback(null, a + b); 174 | }, 1000) 175 | } 176 | 177 | // Run in a fiber 178 | Sync(function(){ 179 | 180 | // asyncFunction returns the result after 1000 ms 181 | var foo = asyncFunction.future(null, 2, 3); 182 | // but we can wait only 500ms! 183 | foo.timeout = 500; 184 | 185 | try { 186 | var result = foo.result; 187 | } 188 | catch (e) { 189 | console.error(e); // Future function timed out at 500 ms 190 | } 191 | 192 | // Same example with straight future function 193 | asyncFunction(2, 3, foo = new Future(500)); 194 | 195 | try { 196 | var result = foo.result; 197 | } 198 | catch (e) { 199 | console.error(e); // Future function timed out at 500 ms 200 | } 201 | }) 202 | ``` 203 | 204 | # How to address non-uniform callbacks 205 | Sometimes third-party libraries are not following convention and passing multiple result parameters to the callback, e.g. `callback(err, recordsets, returnValue)`. In this situation, `node-sync` will simply return array of values instead of value. 206 | 207 | ```javascript 208 | // Asynchronous which returns multiple arguments to a callback and returning a value synchronously 209 | function asyncFunctionReturningMultipleArguments(callback) { 210 | process.nextTick(function(){ 211 | callback(null, 2, 3); 212 | }) 213 | } 214 | 215 | Sync(function(){ 216 | var result = asyncFunctionReturningMultipleArguments.sync(); 217 | assert.equal(result, [2, 3]); 218 | }) 219 | ``` 220 | 221 | See more examples in [examples](https://github.com/0ctave/node-sync/tree/master/examples) directory. 222 | 223 | # Installation 224 | install 225 | 226 | ```bash 227 | $ npm install sync 228 | ``` 229 | and then 230 | 231 | ```bash 232 | $ node your_file_using_sync.js 233 | ``` 234 | -------------------------------------------------------------------------------- /benchmarks/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | /** 4 | * Simple benchmark which compares eventloop speed with Fibers sync 5 | * 6 | * Note: Using process.nextTick in sum() function is not represents the real 7 | * situation. In real live our we use I/O with external sources like 8 | * network or fs. There's no need to use Sync/Fibers with code without I/O. 9 | * So, even if we change process.nextTick to setTimeout() on 3 ms, the result 10 | * will be almost same as native one, because asynchronous function time will 11 | * seriously reduce Sync/Fibers wrapper cost. 12 | * 13 | * The more I/O in your app, the cheaper cost of Fibers. 14 | * 15 | * On Macbook Pro | 2.66 GHz i7 | DDR3 1067 MHz | OSX 10.7.3 | node v0.6.18, node-fibers v0.6.8 16 | * 17 | * Event-loop took 163 ms 18 | * Sync took 486 ms (x2) 19 | * Futures took 7216 ms (x44) 20 | * async() took 542 ms (x3) 21 | * async().sync() took 468 ms (x2) 22 | * Fibers.future took 1452 ms (x8) 23 | * Fiber took 422 ms (x2) 24 | * 25 | * REAL result: 26 | * 27 | * Event-loop took 354 ms 28 | * Sync took 361 ms (x1) 29 | * Futures took 370 ms (x1) 30 | * async() took 353 ms (x0) 31 | * async().sync() took 351 ms (x0) 32 | * Fibers.future took 350 ms (x0) 33 | * Fiber took 350 ms (x0) 34 | */ 35 | 36 | var Sync = require('..'); 37 | 38 | var max = 100000; 39 | 40 | function sum(a, b, callback) { 41 | process.nextTick(function(){ 42 | callback(null, a + b); 43 | }); 44 | } 45 | 46 | var sumAsync = function (a, b, callback) { 47 | var f = Fiber.current; 48 | process.nextTick(function(){ 49 | f.run(a + b); 50 | }); 51 | Fiber.yield(); 52 | }.async(); 53 | 54 | /* REAL 55 | var max = 100; 56 | 57 | var sum = function(a, b, callback) { 58 | setTimeout(function(){ 59 | callback(null, a + b); 60 | }, 3); 61 | } 62 | 63 | var sumAsync = function (a, b, callback) { 64 | var f = Fiber.current; 65 | setTimeout(function(){ 66 | f.run(a + b); 67 | }, 3); 68 | Fiber.yield(); 69 | }.async(); 70 | 71 | */ 72 | 73 | function loop(i, callback) { 74 | sum(3, 4, function(){ 75 | if (i < max) { 76 | loop(i + 1, callback); 77 | } 78 | else { 79 | callback(); 80 | } 81 | }) 82 | } 83 | 84 | var start = new Date(); 85 | loop(0, function(){ 86 | var nativeTime = new Date - start; 87 | console.log('Event-loop took %d ms', nativeTime); 88 | 89 | // Test sync 90 | Sync(function(){ 91 | var start = new Date(); 92 | for(var i = 0; i <= max; i++) { 93 | sum.sync(null, 3, 4); 94 | } 95 | var syncTime = new Date - start; 96 | console.log('Sync took %d ms (x%d)', syncTime, ~~ (syncTime / nativeTime)); 97 | 98 | var start = new Date(); 99 | for(var i = 0; i <= max; i++) { 100 | sum.future(null, 3, 4).yield(); 101 | } 102 | var futureTime = new Date - start; 103 | console.log('Futures took %d ms (x%d)', futureTime, ~~ (futureTime / nativeTime)); 104 | 105 | var start = new Date(); 106 | for(var i = 0; i <= max; i++) { 107 | sumAsync(3, 4); 108 | } 109 | var asyncTime = new Date - start; 110 | console.log('async() took %d ms (x%d)', asyncTime, ~~ (asyncTime / nativeTime)); 111 | 112 | var start = new Date(); 113 | for(var i = 0; i <= max; i++) { 114 | sumAsync.sync(null, 3, 4); 115 | } 116 | var asyncSyncTime = new Date - start; 117 | console.log('async().sync() took %d ms (x%d)', asyncSyncTime, ~~ (asyncSyncTime / nativeTime)); 118 | 119 | var Future = require('fibers/future'); 120 | var sumFuture = Future.wrap(sum); 121 | var start = new Date(); 122 | for(var i = 0; i <= max; i++) { 123 | Future.wait(sumFuture(3, 4)); 124 | } 125 | var fibersFutureTime = new Date - start; 126 | console.log('Fibers.future took %d ms (x%d)', fibersFutureTime, ~~ (fibersFutureTime / nativeTime)); 127 | 128 | // Test Fibers 129 | Fiber(function(){ 130 | var f = Fiber.current; 131 | var start = new Date(); 132 | for(var i = 0; i <= max; i++) { 133 | sum(3, 4, function() { 134 | f.run(); 135 | }); 136 | Fiber.yield(); 137 | } 138 | var fiberTime = new Date - start; 139 | console.log('Fiber took %d ms (x%d)', fiberTime, ~~ (fiberTime / nativeTime)); 140 | }).run(); 141 | }) 142 | }); 143 | 144 | -------------------------------------------------------------------------------- /examples/async.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Example demonstrates how you can use Function.prototype.async() to use any 4 | * prototype function synchronously - transparently binded current object to it 5 | */ 6 | 7 | var Sync = require('..'); 8 | 9 | // Simple asynchronous function 10 | function asyncFunction(a, b, callback) { 11 | process.nextTick(function(){ 12 | callback(null, a + b); 13 | }) 14 | } 15 | 16 | // Example class with assigned value 17 | function SomeClass(a) { 18 | this.a = a; 19 | } 20 | 21 | // Define prototype method in this class, which is synchronous inside 22 | // just turn it to regular asynchronous function using Function.prototype.async() 23 | // if we lack the first argument (context) it will transparently pass current object 24 | // from which the function will be called 25 | SomeClass.prototype.method = function(b) { 26 | return asyncFunction.sync(null, this.a, b); 27 | }.async() // <-- look here 28 | 29 | // Create instance passing 'a' value as argument 30 | var obj = new SomeClass(2); 31 | 32 | // Call our "synchronous" method asynchronously, passing second parameter 'b' 33 | obj.method(3, function(err, result){ 34 | console.log(result); // will print '5' 35 | }); 36 | -------------------------------------------------------------------------------- /examples/blocking.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Example demonstrates that Fibers does not block whole process while yielding 4 | */ 5 | 6 | var Sync = require('..'); 7 | 8 | // Some asynchronous function 9 | function someAsyncFunction(a, b, callback) { 10 | setTimeout(function(){ 11 | callback(null, a + b); 12 | }, 1000) 13 | } 14 | 15 | // Simply print message after 500ms in main loop 16 | setTimeout(function(){ 17 | console.log('500 ms') 18 | }, 500) 19 | 20 | // Here we need to start new Fiber inside of which we can do our tests 21 | Sync(function(){ 22 | 23 | // Call the function synchronously 24 | // current fiber will yield for 1 sec, so it should be returned later than Timeout above 25 | var result = someAsyncFunction.sync(null, 2, 3); 26 | console.log(result); // will print 5 27 | }) 28 | -------------------------------------------------------------------------------- /examples/exceptions.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * This example shows how you can deal with exceptions handling with Sync library 4 | */ 5 | 6 | var Sync = require('..'); 7 | 8 | // Simple asynchronous function which returns and error to a callback 9 | // look at examples/simple.js to see how someAsyncFunction works normally 10 | function someAsyncFunction(a, b, callback) { 11 | setTimeout(function(){ 12 | callback('something went wrong'); 13 | }, 1000) 14 | } 15 | 16 | // Here we need to start new Fiber inside of which we can do our tests 17 | Sync(function(){ 18 | try { 19 | var result = someAsyncFunction.sync(null, 2, 3); 20 | } 21 | catch (e) { 22 | console.error(e); // will print 'something went wrong' after 1 sec 23 | } 24 | }) 25 | 26 | /** 27 | * Another example shows how Sync throws an exception to a callback 28 | * if some error occured inside of 'fn' body 29 | * look at examples/fiber.js for more details about Sync 30 | */ 31 | 32 | // Simple asynchronous function with fiber inside and throws an exception 33 | function someFiberAsyncFunction(file, callback) { 34 | Sync(function(){ 35 | throw new Error('something went wrong again'); 36 | }, callback) 37 | } 38 | 39 | // Call someAsyncFunction in a normal asynchronous way 40 | someFiberAsyncFunction(__filename, function(err, source){ 41 | if (err) return console.error(err); // will print 'something went wrong again' 42 | }) 43 | 44 | // Another example is synchronous function which can be called only inside of a fiber 45 | // and throws an exception inside of it's body 46 | var someSyncFunction = function(file) { 47 | throw new Error('something went wrong synchronously'); 48 | }.async() // <-- Turn someSyncFunction to asynchronous one 49 | 50 | // call it in asynchronous way 51 | someSyncFunction(__filename, function(err, source){ 52 | if (err) return console.error(err); // will print 'something went wrong synchronously' 53 | }) 54 | 55 | /** 56 | * Exceptions inside of a Sync.Future 57 | * see examples/future.js for more details about Sync.Future 58 | */ 59 | Sync(function(){ 60 | 61 | // Here we need to call someAsyncFunction two times with different arguments in parallel 62 | // but wait for both results and only then continue 63 | try { 64 | var result1 = someAsyncFunction.future(null, 2, 2), 65 | result2 = someAsyncFunction.future(null, 3, 3); 66 | result1.yield(); 67 | result2.yield(); 68 | } 69 | catch (e) { 70 | console.error(e); // will print 'something went wrong' after 1 sec 71 | } 72 | }) 73 | -------------------------------------------------------------------------------- /examples/express.js: -------------------------------------------------------------------------------- 1 | var Sync = require('..'), 2 | express = require('express'), 3 | app = express(); 4 | 5 | app.get('/', function(req, res){ 6 | // you can do .sync here! 7 | res.send('Hello Sync World!'); 8 | return true; // ok 9 | }.asyncMiddleware()); 10 | 11 | app.get('/404', function(req, res){ 12 | return null; // next() 13 | }.asyncMiddleware()); 14 | 15 | app.get('/404-2', function(req, res){ 16 | // next() 17 | }.asyncMiddleware()); 18 | 19 | app.get('/500', function(req, res){ 20 | throw new Error("Something went wrong"); // next(new Error(...)) 21 | }.asyncMiddleware()); 22 | 23 | app.listen(3000); 24 | console.log('App is listening on *:%d', 3000); 25 | -------------------------------------------------------------------------------- /examples/fiber.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * This example shows how we can use Fibers in a single peaces of code, 4 | * without wrapping whole application to it 5 | * 6 | * look at examples/simple.js to see the difference in someAsyncFunction 7 | */ 8 | 9 | var Sync = require('..'); 10 | 11 | // Simple asynchronous function with fiber inside example 12 | function someAsyncFunction(file, callback) { 13 | // Here we just wrap the function body to make it synchronous inside 14 | // Sync will execute first argument 'fn' in synchronous way 15 | // and call second argument 'callback' when function returns 16 | // it also calls callback if exception will be thrown inside of 'fn' 17 | Sync(function(){ 18 | var source = require('fs').readFile.sync(null, __filename); 19 | return source; 20 | }, callback) 21 | } 22 | 23 | // Here we call someAsyncFunction in a normal asynchronous way 24 | someAsyncFunction(__filename, function(err, source){ 25 | if (err) return console.error(err); 26 | console.log(String(source)); // prints the sources of __filename 27 | }) 28 | 29 | // Another example is synchronous function which can be called only inside of a fiber 30 | // Here we need to turn someSyncFunction to asynchronous one, to call it outside of a Fiber 31 | // note that .async() method receives 'this' context as first argument and returns new async function instance 32 | // also if someSyncFunction will throw an exception it will trap into a callback as first argument 33 | var someSyncFunction = function(file) { 34 | var source = require('fs').readFile.sync(null, __filename); 35 | return source; 36 | }.async() // <-- look here 37 | 38 | // call it in asynchronous way 39 | someSyncFunction(__filename, function(err, source){ 40 | if (err) return console.error(err); 41 | console.log(String(source)); // prints the sources of __filename 42 | }) 43 | -------------------------------------------------------------------------------- /examples/future.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Future example 4 | * Shows how we can postpone yielding and call multiple functions in parallel 5 | * And then wait for all results in a single point 6 | * 7 | */ 8 | 9 | var Sync = require('..'); 10 | 11 | // Simple asynchronous function example 12 | function someAsyncFunction(a, b, callback) { 13 | setTimeout(function(){ 14 | callback(null, a + b); 15 | }, 1000) 16 | } 17 | 18 | // Here we need to start new Fiber inside of which we can do our tests 19 | Sync(function(){ 20 | 21 | // no-yield here, call asynchronously 22 | // this functions executes in parallel 23 | var foo = someAsyncFunction.future(null, 2, 3); 24 | var bar = someAsyncFunction.future(null, 4, 4); 25 | 26 | // we are immediately here, no blocking 27 | 28 | // foo, bar - our tickets to the future! 29 | console.log(foo); // { [Function: Future] result: [Getter], error: [Getter] } 30 | 31 | // Yield here 32 | console.log(foo.result, bar.result); // '5 8' after 1 sec (not two) 33 | 34 | // Or you can straightly use Sync.Future without wrapper 35 | // This call doesn't blocks 36 | someAsyncFunction(2, 3, foo = new Sync.Future()); 37 | 38 | // foo is a ticket 39 | console.log(foo); // { [Function: Future] result: [Getter], error: [Getter] } 40 | 41 | // Wait for the result 42 | console.log(foo.result); // 5 after 1 sec 43 | 44 | /** 45 | * Timeouts 46 | */ 47 | 48 | // someAsyncFunction returns the result after 1000 ms 49 | var foo = someAsyncFunction.future(null, 2, 3); 50 | // but we can wait only 500ms! 51 | foo.timeout = 500; 52 | 53 | try { 54 | var result = foo.result; 55 | } 56 | catch (e) { 57 | console.error(e.stack); // Future function timed out at 500 ms 58 | } 59 | 60 | // Same example with straight future function 61 | someAsyncFunction(2, 3, foo = new Sync.Future(500)); 62 | 63 | try { 64 | var result = foo.result; 65 | } 66 | catch (e) { 67 | console.error(e.stack); // Future function timed out at 500 ms 68 | } 69 | }) 70 | -------------------------------------------------------------------------------- /examples/object.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * The example of some object which has a property and 4 | * asynchronous method which uses this.someProperty to get it's value 5 | * and we want to call this method synchronously 6 | */ 7 | 8 | var Sync = require('..'); 9 | 10 | // the object 11 | var someObject = { 12 | 13 | someProperty : 2, 14 | 15 | someAsyncMethod : function someAsyncMethod(b, callback) { 16 | var self = this; 17 | setTimeout(function(){ 18 | callback(null, self.someProperty + b); 19 | }, 1000) 20 | } 21 | } 22 | 23 | // Here we need to start new Fiber inside of which we can do our tests 24 | Sync(function(){ 25 | 26 | // Here we need to set 'this' context for someAsyncMethod 27 | // It will add passed argument to someObject.someProperty and return the result 28 | // It's works the same way as Function.prototyle.call 29 | var result = someObject.someAsyncMethod.sync(someObject, 3); 30 | console.log(result); // 5 31 | 32 | }) 33 | -------------------------------------------------------------------------------- /examples/repl.js: -------------------------------------------------------------------------------- 1 | 2 | var Sync = require('../lib/sync'); 3 | 4 | Sync(function(){ 5 | 6 | var r = Sync.repl('Sync repl > '); 7 | r.context.foo = 'bar'; 8 | 9 | }, Sync.log) -------------------------------------------------------------------------------- /examples/scope.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * This simple example shows how you can easily pass variables across fibers tree 4 | * it's very useful when you have concurrent program (http server) which deals with a lot of simultenous requests 5 | * and you need to maintain the context (e.g. req, res variables) for each local execution stack 6 | * without passing it through function arguments endlessly 7 | * 8 | * In this example, the tree will be looking like: 9 | * 10 | * --> Request #1 11 | * Fiber #1 12 | * someGatewayMethod.future() 13 | * Fiber #1.1 14 | * 15 | * --> Request #2 16 | * Fiber #2 17 | * someGatewayMethod.future() 18 | * Fiber #2.1 19 | * 20 | * So, this program will output: 21 | * request #1 22 | * request #2 23 | */ 24 | 25 | var Sync = require('..'); 26 | 27 | var x = function() { 28 | 29 | //Sync.sleep(100); 30 | 31 | throw new Error('hehe'); 32 | console.log(scope.req); 33 | 34 | }.async(); 35 | 36 | var someGatewayMethod = function() { 37 | 38 | var scope = Sync.scope; 39 | 40 | //throw new Error('hehe'); 41 | //Sync.sleep(100); 42 | 43 | //x.future(); 44 | 45 | x.future(); 46 | //x(); 47 | 48 | return; 49 | 50 | setTimeout(function(){ 51 | 52 | //console.log(Fiber.current) 53 | //throw new Error('hehe'); 54 | 55 | var x = function() { 56 | 57 | throw new Error('hehe'); 58 | console.log(scope.req); 59 | 60 | }.async(); 61 | 62 | x.future(); 63 | //x(); 64 | 65 | 66 | }.async(), 100) 67 | 68 | 69 | }.async() 70 | 71 | 72 | // One fiber (e.g. user's http request) 73 | Sync(function(){ 74 | 75 | Sync.scope.req = 'request #1'; 76 | 77 | // future() runs someGatewayMethod in a separate "forked" fiber 78 | someGatewayMethod.future(); 79 | //someGatewayMethod(); 80 | 81 | //console.log(f.error); 82 | 83 | //someGatewayMethod(); 84 | //Sync.waitFutures(); 85 | 86 | }, Sync.log) 87 | 88 | // Another fiber (e.g. user's http request) 89 | /*Sync(function(){ 90 | 91 | Sync.scope.req = 'request #2'; 92 | 93 | // future() runs someGatewayMethod in a separate "forked" fiber 94 | someGatewayMethod.future(); 95 | })*/ -------------------------------------------------------------------------------- /examples/simple.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * This simple example shows how you can run ANY asynchronous function in synchronous manner 4 | */ 5 | 6 | var Sync = require('..'); 7 | 8 | // Simple asynchronous function example 9 | function someAsyncFunction(a, b, callback) { 10 | setTimeout(function(){ 11 | callback(null, a + b); 12 | }, 1000) 13 | } 14 | 15 | // Here we need to start new Fiber inside of which we can do our tests 16 | Sync(function(){ 17 | 18 | // Here we just need to call the method .sync() for synchronous behavior 19 | // (first argument to sync is an object context, we don't need it in this case) 20 | // the 'result' variable will be assigned only after function will return value 21 | var result = someAsyncFunction.sync(null, 2, 3); 22 | console.log(result); // 5 23 | 24 | // Note: thanks to pthreads, other code of the process can 25 | // be executed normally while we are waiting for result of someAsyncFunction 26 | // for example, fs.readFileSync blocks whole process, but with .sync() method we only block current thread: 27 | var source = require('fs').readFile.sync(null, __filename); 28 | console.log(String(source)); // prints the source of this example itself 29 | 30 | }) 31 | -------------------------------------------------------------------------------- /examples/simple2.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * This simple example shows how you can use synchronous function that uses Fibers in asynchronous environment 4 | */ 5 | 6 | var Sync = require('..'); 7 | 8 | // Simple synchronous function example 9 | var someSyncFunction = function(a, b) 10 | { 11 | // It uses yield! 12 | var fiber = Fiber.current; 13 | 14 | setTimeout(function(){ 15 | fiber.run(a + b); 16 | }, 1000); 17 | 18 | return yield(); 19 | 20 | }.async() // <-- ATTENTION: here we make this function asynchronous 21 | 22 | // Here, in regular asynchronous environment we just call this function normally 23 | someSyncFunction(2, 3, function(err, result){ 24 | console.log(result); // will print '5' after 1 sec 25 | }) 26 | 27 | // It also may throw and exception 28 | var someSyncFunctionThrowingException = function() { 29 | throw 'something went wrong'; 30 | }.async() 31 | 32 | // Here, in regular asynchronous environment we just call this function normally 33 | someSyncFunctionThrowingException(function(err){ 34 | console.log(err); // will print 'something went wrong' 35 | }) 36 | 37 | // If we try to call this function without callback, it will throw an error 38 | try { 39 | var result = someSyncFunction(2, 3); 40 | } 41 | catch (e) { 42 | console.log(e); // 'Missing callback as last argument to async function' 43 | } 44 | 45 | // But if we call this function from within synchronous environment (inside of a Fiber), it will run! 46 | Sync(function(){ 47 | var result = someSyncFunction(2, 3); 48 | console.log(result); // will print '5' after 1 sec 49 | }) 50 | -------------------------------------------------------------------------------- /examples/vars.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * This simple example shows how you can easily pass variables across fibers tree 4 | * it's very useful when you have concurrent program (http server) which deals with a lot of simultenous requests 5 | * and you need to maintain the context (e.g. req, res variables) for each local execution stack 6 | * without passing it through function arguments endlessly 7 | * 8 | * In this example, the tree will be looking like: 9 | * 10 | * --> Request #1 11 | * Fiber #1 12 | * someGatewayMethod.future() 13 | * Fiber #1.1 14 | * 15 | * --> Request #2 16 | * Fiber #2 17 | * someGatewayMethod.future() 18 | * Fiber #2.1 19 | * 20 | * So, this program will output: 21 | * request #1 22 | * request #2 23 | */ 24 | 25 | var Sync = require('..'); 26 | 27 | var someGatewayMethod = function() { 28 | 29 | var scope = Sync.scope; 30 | setInterval(function(){ 31 | console.log(scope.req); 32 | }, 1000) 33 | 34 | }.async() 35 | 36 | // One fiber (e.g. user's http request) 37 | Sync(function(){ 38 | 39 | Sync.scope.req = 'request #1'; 40 | 41 | // future() runs someGatewayMethod in a separate "forked" fiber 42 | someGatewayMethod.future(); 43 | }) 44 | 45 | // Another fiber (e.g. user's http request) 46 | Sync(function(){ 47 | 48 | Sync.scope.req = 'request #2'; 49 | 50 | // future() runs someGatewayMethod in a separate "forked" fiber 51 | someGatewayMethod.future(); 52 | }) 53 | -------------------------------------------------------------------------------- /lib/sync.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 Yuriy Bogdanov 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to 6 | deal in the Software without restriction, including without limitation the 7 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | sell copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | IN THE SOFTWARE. 21 | */ 22 | 23 | // use node-fibers module 24 | var Fiber = require('fibers'); 25 | 26 | /** 27 | * sync() method simply turns any asynchronous function to synchronous one 28 | * It receives context object as first param (like Function.prototype.call) 29 | * 30 | */ 31 | Function.prototype.sync = function(obj /* arguments */) { 32 | 33 | var fiber = Fiber.current, 34 | err, result, 35 | yielded = false; 36 | 37 | // Create virtual callback 38 | var syncCallback = function (callbackError, callbackResult, otherArgs) { 39 | // forbid to call twice 40 | if (syncCallback.called) return; 41 | syncCallback.called = true; 42 | 43 | if (callbackError) { 44 | err = callbackError; 45 | } 46 | else if (otherArgs) { 47 | // Support multiple callback result values 48 | result = []; 49 | for (var i = 1, l = arguments.length; i < l; i++) { 50 | result.push(arguments[i]); 51 | } 52 | } 53 | else { 54 | result = callbackResult; 55 | } 56 | 57 | // Resume fiber if yielding 58 | if (yielded) fiber.run(); 59 | } 60 | 61 | // Prepare args (remove first arg and add callback to the end) 62 | // The cycle is used because of slow v8 arguments materialization 63 | for (var i = 1, args = [], l = arguments.length; i < l; i++) { 64 | args.push(arguments[i]); 65 | } 66 | args.push(syncCallback); 67 | 68 | // call async function 69 | this.apply(obj, args); 70 | 71 | // wait for result 72 | if (!syncCallback.called) { 73 | yielded = true; 74 | Fiber.yield(); 75 | } 76 | 77 | // Throw if err 78 | if (err) throw err; 79 | 80 | return result; 81 | } 82 | 83 | /** 84 | * Sync module itself 85 | */ 86 | var Sync = function Sync(fn, callback) 87 | { 88 | if (fn instanceof Function) { 89 | return Sync.Fiber(fn, callback); 90 | } 91 | 92 | // TODO: we can also wrap any object with Sync, in future.. 93 | } 94 | 95 | Sync.stat = { 96 | totalFibers : 0, 97 | activeFibers : 0, 98 | totalFutures : 0, 99 | activeFutures : 0 100 | } 101 | 102 | /** 103 | * This function should be used when you need to turn some peace of code fiberized 104 | * It just wraps your code with Fiber() logic in addition with exceptions handling 105 | */ 106 | Sync.Fiber = function SyncFiber(fn, callback) 107 | { 108 | var parent = Fiber.current; 109 | Sync.stat.totalFibers++; 110 | 111 | var traceError = new Error(); 112 | if (parent) { 113 | traceError.__previous = parent.traceError; 114 | } 115 | 116 | var fiber = Fiber(function(){ 117 | 118 | Sync.stat.activeFibers++; 119 | 120 | var fiber = Fiber.current, 121 | result, 122 | error; 123 | 124 | // Set id to fiber 125 | fiber.id = Sync.stat.totalFibers; 126 | 127 | // Save the callback to fiber 128 | fiber.callback = callback; 129 | 130 | // Register trace error to the fiber 131 | fiber.traceError = traceError; 132 | 133 | // Initialize scope 134 | fiber.scope = {}; 135 | 136 | // Assign parent fiber 137 | fiber.parent = parent; 138 | 139 | // Fiber string representation 140 | fiber.toString = function() { 141 | return 'Fiber#' + fiber.id; 142 | } 143 | 144 | // Fiber path representation 145 | fiber.getPath = function() { 146 | return (fiber.parent ? fiber.parent.getPath() + ' > ' : '' ) 147 | + fiber.toString(); 148 | } 149 | 150 | // Inherit scope from parent fiber 151 | if (parent) { 152 | fiber.scope.__proto__ = parent.scope; 153 | } 154 | 155 | // Add futures support to a fiber 156 | fiber.futures = []; 157 | 158 | fiber.waitFutures = function() { 159 | var results = []; 160 | while (fiber.futures.length) 161 | results.push(fiber.futures.shift().result); 162 | return results; 163 | } 164 | 165 | fiber.removeFuture = function(ticket) { 166 | var index = fiber.futures.indexOf(ticket); 167 | if (~index) 168 | fiber.futures.splice(index, 1); 169 | } 170 | 171 | fiber.addFuture = function(ticket) { 172 | fiber.futures.push(ticket); 173 | } 174 | 175 | // Run body 176 | try { 177 | // call fn and wait for result 178 | result = fn(Fiber.current); 179 | // if there are some futures, wait for results 180 | fiber.waitFutures(); 181 | } 182 | catch (e) { 183 | error = e; 184 | } 185 | 186 | Sync.stat.activeFibers--; 187 | 188 | // return result to the callback 189 | if (callback instanceof Function) { 190 | callback(error, result); 191 | } 192 | else if (error && parent && parent.callback) { 193 | parent.callback(error); 194 | } 195 | else if (error) { 196 | // TODO: what to do with such errors? 197 | throw error; 198 | } 199 | 200 | }); 201 | 202 | fiber.run(); 203 | } 204 | 205 | /** 206 | * Future object itself 207 | */ 208 | function SyncFuture(timeout) 209 | { 210 | var self = this; 211 | 212 | this.resolved = false; 213 | this.fiber = Fiber.current; 214 | this.yielding = false; 215 | this.timeout = timeout; 216 | this.time = null; 217 | 218 | this._timeoutId = null; 219 | this._result = undefined; 220 | this._error = null; 221 | this._start = +new Date; 222 | 223 | Sync.stat.totalFutures++; 224 | Sync.stat.activeFutures++; 225 | 226 | // Create timeout error to capture stack trace correctly 227 | self.timeoutError = new Error(); 228 | Error.captureStackTrace(self.timeoutError, arguments.callee); 229 | 230 | this.ticket = function Future() 231 | { 232 | // clear timeout if present 233 | if (self._timeoutId) clearTimeout(self._timeoutId); 234 | // measure time 235 | self.time = new Date - self._start; 236 | 237 | // forbid to call twice 238 | if (self.resolved) return; 239 | self.resolved = true; 240 | 241 | // err returned as first argument 242 | var err = arguments[0]; 243 | if (err) { 244 | self._error = err; 245 | } 246 | else { 247 | self._result = arguments[1]; 248 | } 249 | 250 | // remove self from current fiber 251 | self.fiber.removeFuture(self.ticket); 252 | Sync.stat.activeFutures--; 253 | 254 | if (self.yielding && Fiber.current !== self.fiber) { 255 | self.yielding = false; 256 | self.fiber.run(); 257 | } 258 | else if (self._error) { 259 | throw self._error; 260 | } 261 | } 262 | 263 | this.ticket.__proto__ = this; 264 | 265 | this.ticket.yield = function() { 266 | while (!self.resolved) { 267 | self.yielding = true; 268 | if (self.timeout) { 269 | self._timeoutId = setTimeout(function(){ 270 | self.timeoutError.message = 'Future function timed out at ' + self.timeout + ' ms'; 271 | self.ticket(self.timeoutError); 272 | }, self.timeout) 273 | } 274 | Fiber.yield(); 275 | } 276 | if (self._error) throw self._error; 277 | return self._result; 278 | } 279 | 280 | this.ticket.__defineGetter__('result', function(){ 281 | return this.yield(); 282 | }); 283 | 284 | this.ticket.__defineGetter__('error', function(){ 285 | if (self._error) { 286 | return self._error; 287 | } 288 | try { 289 | this.result; 290 | } 291 | catch (e) { 292 | return e; 293 | } 294 | return null; 295 | }); 296 | 297 | this.ticket.__defineGetter__('timeout', function(){ 298 | return self.timeout; 299 | }); 300 | 301 | this.ticket.__defineSetter__('timeout', function(value){ 302 | self.timeout = value; 303 | }); 304 | 305 | // append self to current fiber 306 | this.fiber.addFuture(this.ticket); 307 | 308 | return this.ticket; 309 | } 310 | 311 | SyncFuture.prototype.__proto__ = Function; 312 | Sync.Future = SyncFuture; 313 | 314 | /** 315 | * Calls the function asynchronously and yields only when 'value' or 'error' getters called 316 | * Returs Future function/object (promise) 317 | * 318 | */ 319 | Function.prototype.future = function(obj /* arguments */) { 320 | 321 | var fn = this, 322 | future = new SyncFuture(); 323 | 324 | // Prepare args (remove first arg and add callback to the end) 325 | // The cycle is used because of slow v8 arguments materialization 326 | for (var i = 1, args = [], l = arguments.length; i < l; i++) { 327 | args.push(arguments[i]); 328 | } 329 | // virtual future callback, push it as last argument 330 | args.push(future); 331 | 332 | // call async function 333 | fn.apply(obj, args); 334 | 335 | return future; 336 | } 337 | 338 | /** 339 | * Use this method to make asynchronous function from synchronous one 340 | * This is a opposite function from .sync() 341 | */ 342 | Function.prototype.async = function(context) 343 | { 344 | var fn = this, fiber = Fiber.current; 345 | 346 | function asyncFunction() { 347 | 348 | // Prepare args (remove first arg and add callback to the end) 349 | // The cycle is used because of slow v8 arguments materialization 350 | for (var i = 0, args = [], l = arguments.length; i < l; i++) { 351 | args.push(arguments[i]); 352 | } 353 | 354 | var obj = context || this, 355 | cb = args.pop(), 356 | async = true; 357 | 358 | if (typeof(cb) !== 'function') { 359 | args.push(cb); 360 | if (Fiber.current) async = false; 361 | } 362 | 363 | Fiber.current = Fiber.current || fiber; 364 | 365 | // Call asynchronously 366 | if (async) { 367 | Sync(function(){ 368 | return fn.apply(obj, args); 369 | }, cb); 370 | } 371 | // Call synchronously in same fiber 372 | else { 373 | return fn.apply(obj, args); 374 | } 375 | } 376 | 377 | // Do nothing on async again 378 | asyncFunction.async = function() { 379 | return asyncFunction; 380 | } 381 | // Override sync call 382 | asyncFunction.sync = function(obj) { 383 | for (var i = 1, args = [], l = arguments.length; i < l; i++) { 384 | args.push(arguments[i]); 385 | } 386 | return fn.apply(obj || context || this, args); 387 | } 388 | // Override toString behavior 389 | asyncFunction.toString = function() { 390 | return fn + '.async()'; 391 | } 392 | 393 | return asyncFunction; 394 | } 395 | 396 | /** 397 | * Used for writing synchronous middleware-style functions 398 | * 399 | * throw "something" --> next('something') 400 | * return --> next() 401 | * return null --> next() 402 | * return undefined --> next() 403 | * return true --> void 404 | */ 405 | Function.prototype.asyncMiddleware = function(obj){ 406 | var fn = this.async(obj); 407 | // normal (req, res) middleware 408 | if (this.length === 2) { 409 | return function(req, res, next) { 410 | return fn.call(this, req, res, function(err, result){ 411 | if (err) return next(err); 412 | if (result !== true) next(); 413 | }); 414 | } 415 | } 416 | // error handling (err, req, res) middleware 417 | else if (this.length === 3) { 418 | return function(err, req, res, next) { 419 | return fn.call(this, err, req, res, function(err, result){ 420 | if (err) return next(err); 421 | if (result !== true) next(); 422 | }); 423 | } 424 | } 425 | } 426 | 427 | /** 428 | * Sleeps current fiber on given value of millis 429 | */ 430 | Sync.sleep = function(ms) 431 | { 432 | var fiber = Fiber.current; 433 | if (!fiber) { 434 | throw new Error('Sync.sleep() can be called only inside of fiber'); 435 | } 436 | 437 | setTimeout(function(){ 438 | fiber.run(); 439 | }, ms); 440 | 441 | Fiber.yield(); 442 | } 443 | 444 | /** 445 | * Logs sync result 446 | */ 447 | Sync.log = function(err, result) 448 | { 449 | if (err) return console.error(err.stack || err); 450 | if (arguments.length == 2) { 451 | if (result === undefined) return; 452 | return console.log(result); 453 | } 454 | console.log(Array.prototyle.slice.call(arguments, 1)); 455 | } 456 | 457 | /** 458 | * Synchronous repl implementation: each line = new fiber 459 | */ 460 | Sync.repl = function() { 461 | 462 | var repl = require('repl'); 463 | 464 | // Start original repl 465 | var r = repl.start.apply(repl, arguments); 466 | 467 | // Wrap line watchers with Fiber 468 | var newLinsteners = [] 469 | r.rli.listeners('line').map(function(f){ 470 | newLinsteners.push(function(a){ 471 | Sync(function(){ 472 | require.cache[__filename] = module; 473 | f(a); 474 | }, Sync.log) 475 | }) 476 | }) 477 | r.rli.removeAllListeners('line'); 478 | while (newLinsteners.length) { 479 | r.rli.on('line', newLinsteners.shift()); 480 | } 481 | 482 | // Assign Sync to repl context 483 | r.context.Sync = Sync; 484 | 485 | return r; 486 | }; 487 | 488 | // TODO: document 489 | Sync.__defineGetter__('scope', function() { 490 | return Fiber.current && Fiber.current.scope; 491 | }) 492 | 493 | // TODO: document 494 | Sync.waitFutures = function() { 495 | if (Fiber.current) { 496 | Fiber.current.waitFutures(); 497 | } 498 | } 499 | 500 | // Expose Fibers 501 | Sync.Fibers = Fiber; 502 | 503 | module.exports = exports = Sync; 504 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sync", 3 | "description": "Library that makes simple to run asynchronous functions in synchronous manner, using node-fibers.", 4 | "version": "0.2.5", 5 | "url": "http://github.com/0ctave/node-sync", 6 | "author": "Yuriy Bogdanov ", 7 | "main": "lib/sync", 8 | "license": "MIT", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/ybogdanov/node-sync.git" 12 | }, 13 | "engines": { 14 | "node": ">=0.5.2" 15 | }, 16 | "dependencies": { 17 | "fibers": ">=0.6" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/async.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Tests for Function.prototype.async 4 | */ 5 | 6 | var Sync = require('..'), 7 | Fiber = require('fibers'), 8 | assert = require('assert'); 9 | 10 | // Simple asynchronous function 11 | function asyncFunction(a, b, callback) { 12 | process.nextTick(function(){ 13 | callback(null, a + b); 14 | }) 15 | } 16 | 17 | // Simple synchronous function 18 | function syncFunction(a, b) { 19 | return asyncFunction.sync(null, a, b); 20 | } 21 | 22 | // Simple synchronous function throws an exception 23 | function syncFunctionThrowsException(a, b) { 24 | throw 'something went wrong'; 25 | } 26 | 27 | // test object 28 | var testObject = { 29 | 30 | property : 2, 31 | 32 | syncMethod : function someAsyncMethod(b) { 33 | return asyncFunction.sync(null, this.property, b); 34 | }, 35 | 36 | syncMethodThrowsException : function someAsyncMethodThrowsException(b) { 37 | throw 'something went wrong'; 38 | } 39 | } 40 | 41 | var runTest = module.exports = function(callback) 42 | { 43 | var e; 44 | 45 | try { 46 | 47 | // test on returning value 48 | var syncFunctionAsync = syncFunction.async(); 49 | syncFunctionAsync(2, 3, function(err, result){ 50 | assert.equal(result, 2 + 3); 51 | }) 52 | 53 | // test on throws exception 54 | var syncFunctionThrowsExceptionAsync = syncFunctionThrowsException.async(); 55 | syncFunctionThrowsExceptionAsync(2, 3, function(err, result){ 56 | assert.equal(err, 'something went wrong'); 57 | }) 58 | 59 | // test on throws exception when call without callback 60 | /*var syncFunctionAsync = syncFunction.async(); 61 | assert.throws(function(){ 62 | syncFunctionAsync(2, 3); 63 | }, 'Missing callback as last argument to async function');*/ 64 | 65 | // test on working synchronously within a Fiber 66 | Fiber(function(){ 67 | var result = syncFunctionAsync(2, 3); 68 | assert.equal(result, 5); 69 | }).run() 70 | 71 | // test on working synchronously within a Fiber with object context 72 | Fiber(function(){ 73 | testObject.syncMethodAuto = testObject.syncMethod.async(); 74 | var result = testObject.syncMethodAuto(3); 75 | assert.equal(result, testObject.property + 3); 76 | }).run() 77 | 78 | // test running in a same fiber 79 | Fiber(function(){ 80 | var fiber = Fiber.current; 81 | (function(){ 82 | assert.ok(Fiber.current instanceof Fiber); 83 | assert.strictEqual(Fiber.current, fiber); 84 | }).async()(); 85 | }).run() 86 | 87 | // test on returning value with object context 88 | var syncMethodAsync = testObject.syncMethod.async(testObject); 89 | syncMethodAsync(3, function(err, result){ 90 | try { 91 | assert.equal(result, testObject.property + 3); 92 | } 93 | catch (e){ 94 | console.error(e.stack); 95 | } 96 | }) 97 | 98 | // test automatic context assignment 99 | testObject.syncMethodAuto = testObject.syncMethod.async(); 100 | testObject.syncMethodAuto(3, function(err, result){ 101 | assert.equal(result, testObject.property + 3); 102 | }) 103 | 104 | // test on throws exception with object context 105 | var syncMethodThrowsExceptionAsync = testObject.syncMethodThrowsException.async(testObject); 106 | syncMethodThrowsExceptionAsync(3, function(err, result){ 107 | assert.equal(err, 'something went wrong'); 108 | }) 109 | 110 | // Test async call with .sync() 111 | Fiber(function(){ 112 | var syncFunctionAsync = syncFunction.async(); 113 | var result = syncFunctionAsync.sync(null, 2, 3); 114 | assert.equal(result, 2 + 3); 115 | }).run() 116 | 117 | // Test async call with .sync() throwing exception 118 | Fiber(function(){ 119 | var syncFunctionThrowsExceptionAsync = syncFunctionThrowsException.async(); 120 | assert.throws(function(){ 121 | syncFunctionThrowsExceptionAsync.sync(null, 2, 3); 122 | }, 'something went wrong'); 123 | }).run() 124 | 125 | // Test async call with .sync() with object context 1 126 | Fiber(function(){ 127 | var syncMethodAsync = testObject.syncMethod.async(testObject); 128 | var result = syncMethodAsync.sync(testObject, 3); 129 | assert.equal(result, testObject.property + 3); 130 | }).run() 131 | 132 | // Test async call with .sync() with object context 2 133 | Fiber(function(){ 134 | var syncMethodAsync = testObject.syncMethod.async(testObject); 135 | var result = syncMethodAsync.sync(null, 3); 136 | assert.equal(result, testObject.property + 3); 137 | }).run() 138 | 139 | // Test async call with .future() 140 | Sync(function(){ 141 | var syncFunctionAsync = syncFunction.async(); 142 | var future = syncFunctionAsync.future(null, 2, 3); 143 | assert.equal(future.result, 2 + 3); 144 | }, function(err){ 145 | if (err) console.error(err); 146 | }) 147 | 148 | // Test async call with .future() throwing exception 149 | Sync(function(){ 150 | var syncFunctionThrowsExceptionAsync = syncFunctionThrowsException.async(); 151 | assert.throws(function(){ 152 | var future = syncFunctionThrowsExceptionAsync.future(null, 2, 3); 153 | future.result; 154 | }, 'something went wrong'); 155 | }, function(err){ 156 | if (err) console.error(err); 157 | }) 158 | 159 | // Test async call with .future() with object context 160 | Sync(function(){ 161 | var syncMethodAsync = testObject.syncMethod.async(testObject); 162 | var future = syncMethodAsync.future(testObject, 3); 163 | assert.equal(future.result, testObject.property + 3); 164 | }, function(err){ 165 | if (err) console.error(err); 166 | }) 167 | 168 | // Test async call with .future() with object context 2 169 | Sync(function(){ 170 | var syncMethodAsync = testObject.syncMethod.async(testObject); 171 | var future = syncMethodAsync.future(null, 3); 172 | assert.equal(future.result, testObject.property + 3); 173 | }, function(err){ 174 | if (err) console.error(err); 175 | }) 176 | 177 | // Test async call in the same fiber 178 | Sync(function(){ 179 | 180 | var result; 181 | 182 | var someSyncFunction = function() { 183 | 184 | result = asyncFunction.sync(null, 2, 3); 185 | 186 | }.async() 187 | 188 | var fiber = Fiber.current; 189 | 190 | process.nextTick(function(){ 191 | someSyncFunction(); 192 | 193 | process.nextTick(function(){ 194 | fiber.run(); 195 | }) 196 | }) 197 | 198 | Fiber.yield(); 199 | 200 | assert.equal(result, 5); 201 | 202 | }, function(err){ 203 | if (err) console.error(err); 204 | }) 205 | 206 | } 207 | catch (e) { 208 | console.error(e.stack); 209 | } 210 | 211 | if (callback) { 212 | callback(e); 213 | } 214 | } 215 | 216 | if (!module.parent) { 217 | runTest(function(){ 218 | console.log('%s done', __filename); 219 | }); 220 | } 221 | -------------------------------------------------------------------------------- /test/fiber.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Tests for Sync function 4 | */ 5 | 6 | var Sync = require('..'), 7 | Fiber = require('fibers'), 8 | assert = require('assert'); 9 | 10 | var runTest = module.exports = function(callback) 11 | { 12 | var e; 13 | 14 | try { 15 | 16 | // Test returning value 17 | Sync(function(){ 18 | return 'some value'; 19 | }, function(err, value){ 20 | assert.equal(value, 'some value'); 21 | }) 22 | 23 | // Test throws an exception 24 | Sync(function(){ 25 | throw 'something went wrong'; 26 | }, function(err){ 27 | assert.equal(err, 'something went wrong'); 28 | }) 29 | 30 | // Test throws exception without callback 31 | // Update: do not throw exception if no callback 32 | // assert.throws(function(){ 33 | // Sync(function(){ 34 | // throw 'something went wrong'; 35 | // }) 36 | // }, 'something went wrong'); 37 | 38 | // Test callback throws exception 39 | assert.throws(function(){ 40 | Sync(function(){ 41 | 42 | }, function(){ 43 | throw 'something went wrong'; 44 | }) 45 | }, 'something went wrong'); 46 | 47 | // Test fiber passing 48 | Sync(function(fiber){ 49 | assert.ok(fiber instanceof Fiber); 50 | }) 51 | 52 | // Test without callback 53 | assert.doesNotThrow(function(){ 54 | Sync(function(){ 55 | return 'test'; 56 | }) 57 | }) 58 | 59 | // Test backwards capability 60 | Sync.Fiber(function(){ 61 | return 'some value'; 62 | }, function(err, value){ 63 | assert.equal(value, 'some value'); 64 | }) 65 | } 66 | catch (e) { 67 | console.error(e.stack); 68 | } 69 | 70 | if (callback) { 71 | callback(e); 72 | } 73 | } 74 | 75 | if (!module.parent) { 76 | runTest(function(){ 77 | console.log('%s done', __filename); 78 | }); 79 | } 80 | -------------------------------------------------------------------------------- /test/future.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Tests for Function.prototype.future 4 | */ 5 | 6 | var Sync = require('..'), 7 | Fiber = require('fibers'), 8 | assert = require('assert'); 9 | 10 | // Simple asynchronous function 11 | function asyncFunction(a, b, callback) { 12 | process.nextTick(function(){ 13 | callback(null, a + b); 14 | }) 15 | } 16 | 17 | // asynchronous with timeout 18 | function asyncFunctionTimeout(t, callback) { 19 | setTimeout(function(){ 20 | callback(null, 'result'); 21 | }, t) 22 | } 23 | 24 | // synchronous with timeout 25 | function syncFunctionTimeout(t) { 26 | var fiber = Fiber.current; 27 | setTimeout(function(){ 28 | fiber.run('result'); 29 | }, t) 30 | return Fiber.yield(); 31 | } 32 | 33 | // Simple asynchronous function which throws an exception 34 | function asyncFunctionThrowsException(a, b, callback) { 35 | process.nextTick(function(){ 36 | callback('something went wrong'); 37 | }) 38 | } 39 | 40 | // Wrong asynchronous which calls callback twice 41 | function asyncFunctionCallbackTwice(a, b, callback) { 42 | process.nextTick(function(){ 43 | callback(null, a + b); 44 | callback(null, a - b); 45 | }) 46 | } 47 | 48 | // test object 49 | var testObject = { 50 | 51 | property : 2, 52 | 53 | asyncMethod : function someAsyncMethod(b, callback) { 54 | var self = this; 55 | process.nextTick(function(){ 56 | callback(null, self.property + b); 57 | }) 58 | }, 59 | 60 | asyncMethodThrowsException : function someAsyncMethodThrowsException(b, callback) { 61 | process.nextTick(function(){ 62 | callback('something went wrong'); 63 | }) 64 | } 65 | } 66 | 67 | var runTest = module.exports = function(callback) 68 | { 69 | Sync(function(){ 70 | 71 | // test on returning value 72 | var future = asyncFunction.future(null, 2, 3); 73 | // check future function 74 | assert.ok(future instanceof Sync.Future); 75 | assert.ok(future instanceof Function); 76 | // check future result 77 | assert.equal(future.result, 2 + 3); 78 | // check error 79 | assert.strictEqual(future.error, null); 80 | 81 | // test on returning value 82 | var future = asyncFunction.future(null, 2, 3); 83 | // check future result 84 | assert.equal(future.yield(), 2 + 3); 85 | // check error 86 | assert.strictEqual(future.error, null); 87 | 88 | // check yield on error getter 89 | var future = asyncFunction.future(null, 2, 3); 90 | // check error 91 | assert.strictEqual(future.error, null); 92 | // check future result 93 | assert.equal(future.result, 2 + 3); 94 | 95 | // test on throws exception 96 | var future = asyncFunctionThrowsException.future(null, 2, 3); 97 | assert.throws(function(){ 98 | future.result; 99 | }, 'something went wrong'); 100 | // check error 101 | assert.ok(future.error); 102 | 103 | // test asynchronous which calls callback twice (should not be called twice) 104 | var future = asyncFunctionCallbackTwice.future(null, 2, 3); 105 | assert.equal(future.result, 2 + 3); 106 | 107 | // test on returning value with object context 108 | var future = testObject.asyncMethod.future(testObject, 3); 109 | assert.equal(future.result, testObject.property + 3); 110 | 111 | // test on throws exception with object context 112 | var future = testObject.asyncMethodThrowsException.future(testObject, 2); 113 | assert.throws(function(){ 114 | future.result; 115 | }, 'something went wrong'); 116 | 117 | // test straight Sync.Future usage 118 | asyncFunction(2, 3, future = new Sync.Future()); 119 | // check error 120 | assert.strictEqual(future.error, null); 121 | // check future result 122 | assert.equal(future.result, 2 + 3); 123 | 124 | // test two futures goes in parallel 125 | var start = new Date(); 126 | var future1 = asyncFunctionTimeout.future(null, 100); 127 | var future2 = asyncFunctionTimeout.future(null, 100); 128 | assert.ok(future1.result); 129 | assert.ok(future2.result); 130 | var duration = new Date - start; 131 | assert.ok(duration < 110); 132 | 133 | // test two async() futures goes in parallel 134 | var start = new Date(); 135 | var future1 = syncFunctionTimeout.async().future(null, 100); 136 | var future2 = syncFunctionTimeout.async().future(null, 100); 137 | assert.ok(future1.result); 138 | assert.ok(future2.result); 139 | var duration = new Date - start; 140 | assert.ok(duration < 110); 141 | 142 | // Test futures are automatically resolved when Fiber ends 143 | var futures = []; 144 | Sync(function(){ 145 | futures.push(asyncFunction.future(null, 2, 3)); 146 | futures.push(asyncFunction.future(null, 2, 3)); 147 | }, function(err){ 148 | if (err) return console.error(err); 149 | try { 150 | while (futures.length) assert.ok(futures.shift().resolved); 151 | } 152 | catch (e) { 153 | console.error(e); 154 | } 155 | }) 156 | 157 | // Test timeout 158 | var future = asyncFunctionTimeout.future(null, 100); 159 | future.timeout = 200; 160 | // check future result 161 | assert.equal(future.result, 'result'); 162 | // check error 163 | assert.strictEqual(future.error, null); 164 | 165 | // Test timeout error 166 | var future = asyncFunctionTimeout.future(null, 100); 167 | future.timeout = 50; 168 | 169 | assert.throws(function(){ 170 | future.result; 171 | }, 'future should throw timeout exception') 172 | 173 | // check error 174 | assert.ok(future.error instanceof Error); 175 | assert.ok(~future.error.stack.indexOf(__filename)); 176 | 177 | // test straight Sync.Future timeout usage 178 | asyncFunctionTimeout(100, future = new Sync.Future(200)); 179 | // check error 180 | assert.strictEqual(future.error, null); 181 | // check future result 182 | assert.equal(future.result, 'result'); 183 | 184 | // test straight Sync.Future timeout error 185 | asyncFunctionTimeout(100, future = new Sync.Future(50)); 186 | assert.throws(function(){ 187 | future.result; 188 | }, 'future should throw timeout exception') 189 | 190 | // check error 191 | assert.ok(future.error instanceof Error); 192 | assert.ok(~future.error.stack.indexOf(__filename)); 193 | 194 | // TODO: test multiple future calls with errors 195 | return; 196 | 197 | var foo = function(a, b, callback) 198 | { 199 | process.nextTick(function(){ 200 | callback('error'); 201 | }) 202 | } 203 | 204 | var fn = function() 205 | { 206 | var future = foo.future(null, 2, 3); 207 | var future2 = foo.future(null, 2, 3); 208 | console.log('x'); 209 | var a = future.result; 210 | console.log('y'); 211 | var b = future2.result; 212 | 213 | }.async() 214 | 215 | Sync(function(){ 216 | 217 | try { 218 | fn.sync(); 219 | } 220 | catch (e) { 221 | console.log('catched', e.stack); 222 | } 223 | 224 | }, function(err){ 225 | if (err) console.error('hehe', err); 226 | }) 227 | 228 | }, function(e){ 229 | if (e) { 230 | console.error(e.stack); 231 | } 232 | if (callback) { 233 | callback(e); 234 | } 235 | }) 236 | } 237 | 238 | if (!module.parent) { 239 | runTest(function(){ 240 | console.log('%s done', __filename); 241 | }); 242 | } 243 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Tests suite - runs all tests 4 | */ 5 | 6 | var fs = require('fs'); 7 | 8 | var tests = ['fiber', 'sync', 'async', 'future', 'sleep']; 9 | var i = 0; 10 | tests.forEach(function(name){ 11 | 12 | var test = require('./' + name); 13 | test(function(err){ 14 | if (err) console.log('test %s failed', name); 15 | else console.log('%s passed', name); 16 | 17 | if (++i == tests.length) { 18 | console.log('done'); 19 | } 20 | }); 21 | }) 22 | -------------------------------------------------------------------------------- /test/sleep.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Tests for Sync.sleep function 4 | */ 5 | 6 | var Sync = require('..'), 7 | assert = require('assert'); 8 | 9 | var runTest = module.exports = function(callback) 10 | { 11 | var e; 12 | 13 | try { 14 | // Test sleeping correctly 15 | Sync(function(){ 16 | var start = new Date; 17 | Sync.sleep(101); // sleep on 100 ms 18 | 19 | assert.ok(new Date - start >= 100); 20 | }) 21 | 22 | // Test throws exception when callend not insode of fiber 23 | assert.throws(function(){ 24 | Sync.sleep(1000); 25 | }, 'should throw exception when callend not inside of fiber') 26 | } 27 | catch (e) { 28 | console.error(e.stack); 29 | } 30 | 31 | if (callback) { 32 | callback(e); 33 | } 34 | } 35 | 36 | if (!module.parent) { 37 | runTest(function(){ 38 | console.log('%s done', __filename); 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /test/sync.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Tests for Function.prototype.sync 4 | */ 5 | 6 | var Sync = require('..'), 7 | assert = require('assert'); 8 | 9 | // Simple asynchronous function 10 | function asyncFunction(a, b, callback) { 11 | process.nextTick(function(){ 12 | callback(null, a + b); 13 | }) 14 | } 15 | 16 | // Simple asynchronous function which invokes callback in the same tick 17 | function asyncFunctionSync(a, b, callback) { 18 | callback(null, a + b); 19 | } 20 | 21 | // Simple asynchronous function returning a value synchronously 22 | function asyncFunctionReturningValue(a, b, callback) { 23 | process.nextTick(function(){ 24 | callback(null, a + b); 25 | }) 26 | return 123; 27 | } 28 | 29 | // Asynchronous which returns multiple arguments to a callback and returning a value synchronously 30 | function asyncFunctionReturningValueMultipleArguments(a, b, callback) { 31 | process.nextTick(function(){ 32 | callback(null, a, b); 33 | }) 34 | return 123; 35 | } 36 | 37 | // Simple asynchronous function which throws an exception 38 | function asyncFunctionThrowsException(a, b, callback) { 39 | process.nextTick(function(){ 40 | callback('something went wrong'); 41 | }) 42 | } 43 | 44 | // Simple asynchronous function which throws an exception in the same tick 45 | function asyncFunctionThrowsExceptionSync(a, b, callback) { 46 | callback('something went wrong'); 47 | } 48 | 49 | // Simple asynchronous function which throws an exception and returning a value synchronously 50 | function asyncFunctionReturningValueThrowsException(a, b, callback) { 51 | process.nextTick(function(){ 52 | callback('something went wrong'); 53 | }) 54 | return 123; 55 | } 56 | 57 | // Wrong asynchronous which calls callback twice 58 | function asyncFunctionCallbackTwice(a, b, callback) { 59 | process.nextTick(function(){ 60 | callback(null, a + b); 61 | callback(null, a - b); 62 | }) 63 | } 64 | 65 | // Asynchronous which returns multiple arguments to a callback 66 | function asyncFunctionMultipleArguments(a, b, callback) { 67 | process.nextTick(function(){ 68 | callback(null, a, b); 69 | }) 70 | } 71 | 72 | // test object 73 | var testObject = { 74 | 75 | property : 2, 76 | 77 | asyncMethod : function someAsyncMethod(b, callback) { 78 | var self = this; 79 | process.nextTick(function(){ 80 | callback(null, self.property + b); 81 | }) 82 | }, 83 | 84 | asyncMethodThrowsException : function someAsyncMethodThrowsException(b, callback) { 85 | process.nextTick(function(){ 86 | callback('something went wrong'); 87 | }) 88 | }, 89 | 90 | asyncMethodMultipleArguments : function asyncMethodMultipleArguments(b, callback) { 91 | var self = this; 92 | process.nextTick(function(){ 93 | callback(null, self.property, b); 94 | }) 95 | } 96 | } 97 | 98 | var runTest = module.exports = function(callback) 99 | { 100 | Sync(function(){ 101 | 102 | // test on returning value 103 | var result = asyncFunction.sync(null, 2, 3); 104 | assert.equal(result, 2 + 3); 105 | 106 | // test on returning value in the same tick 107 | var result = asyncFunctionSync.sync(null, 2, 3); 108 | assert.equal(result, 2 + 3); 109 | 110 | // test on throws exception 111 | assert.throws(function(){ 112 | var result = asyncFunctionThrowsException.sync(null, 2, 3); 113 | }, 'something went wrong'); 114 | 115 | // test on throws exception in the same tick 116 | assert.throws(function(){ 117 | var result = asyncFunctionThrowsExceptionSync.sync(null, 2, 3); 118 | }, 'something went wrong'); 119 | 120 | // test asynchronous function should not return a synchronous value 121 | var result = asyncFunctionReturningValue.sync(null, 2, 3); 122 | assert.equal(result, 2 + 3); 123 | 124 | // test returning multiple arguments 125 | var result = asyncFunctionMultipleArguments.sync(null, 2, 3); 126 | assert.deepEqual(result, [2, 3]); 127 | 128 | // test asynchronous function should not return a synchronous value (multiple arguments) 129 | var result = asyncFunctionReturningValueMultipleArguments.sync(null, 2, 3); 130 | assert.deepEqual(result, [2, 3]); 131 | 132 | // test asynchronous function should not return a synchronous value (throwing exception) 133 | assert.throws(function(){ 134 | var result = asyncFunctionReturningValueThrowsException.sync(null, 2, 3); 135 | }, 'something went wrong'); 136 | 137 | // test asynchronous which calls callback twice (should not be called twice) 138 | var result = asyncFunctionCallbackTwice.sync(null, 2, 3); 139 | assert.equal(result, 2 + 3); 140 | 141 | // test on returning value with object context 142 | var result = testObject.asyncMethod.sync(testObject, 3); 143 | assert.equal(result, testObject.property + 3); 144 | 145 | // test on throws exception with object context 146 | assert.throws(function(){ 147 | var result = testObject.asyncMethodThrowsException.sync(testObject, 2); 148 | }, 'something went wrong'); 149 | 150 | // test returning multiple arguments with object context 151 | var result = testObject.asyncMethodMultipleArguments.sync(testObject, 3); 152 | assert.deepEqual(result, [testObject.property, 3]); 153 | 154 | // test double-callback 155 | var result = function(callback) { 156 | callback(null, 1); 157 | callback(null, 2); 158 | }.sync() 159 | 160 | assert.strictEqual(1, result); 161 | 162 | // test undefined result 163 | // test double-callback 164 | var result = function(callback) { 165 | callback(); 166 | }.sync() 167 | 168 | assert.strictEqual(undefined, result); 169 | 170 | }, function(e){ 171 | if (e) { 172 | console.error(e.stack); 173 | } 174 | if (callback) { 175 | callback(e); 176 | } 177 | }) 178 | } 179 | 180 | if (!module.parent) { 181 | runTest(function(){ 182 | console.log('%s done', __filename); 183 | }); 184 | } 185 | --------------------------------------------------------------------------------