📜 ⬆️ ⬇️

node-sync - pseudo-synchronous programming on nodejs using fibers

Node was published library node-fibers , contributing to nodejs and v8 support for remarkable fiber / coroutine - that is, the ability to use yield.
In parallel, a host of discussions took place on nodejs groups on various options for simplifying asynchronous syntax.

Inspired by the possibilities offered by the “fibers”, I wrote the node-sync library, which makes development in the asynchronous environment of nodejs much more convenient, and the code is clearer.

Synopsis

//   ,  callback    1  function someAsyncFunction(a, b, callback) { setTimeout(function(){ callback(null, a + b); }, 1000) } //    ,  Function.prototype.sync(), //     ,   call() //      ""  ,      var result = someAsyncFunction.sync(null, 2, 3); console.log(result); // "5"  1  


Philosophy


The main idea is that the Function.prototype.sync () method is built into any function by default, and its interface also corresponds to the well-known call (). By connecting the sync library, we can call any asynchronous function synchronously, without writing additional code.
Pseudo-synchronous programming - because in fact, Function.prototype.sync () does not block the whole process, but only the current thread. The body of the function itself is executed asynchronously, we are just waiting for the result (using “yield”). But at the same time, the code is read "synchronously."
')
node-sync solves three important questions for me:
1. Exemption from endless indentation with callbacks (eliminates “spaghetti code”)
2. Correct error handling
3. Integration with existing code / libraries without the need for refactoring

For more than a month I have been using this node-sync in my application, the transition was unnoticeable - I just started writing new code in a “pseudo-synchronous” manner, the old code has remained the same.

Lock


The beauty of "fibers" is that when waiting for (yield) only the current flow is blocked, and not the whole process. A vivid example of blocking the whole process is fs.readFileSync and other pseudo-synchronous functions.

Using "fibers" you can avoid global blocking and, at the same time, read the file synchronously:
 var fs = require('fs'), Sync = require('sync'); //    Sync(function(){ //   --> //   ,  Function.prototype.sync() var source = fs.readFile.sync(null, __filename); //     console.log(String(source)); }) 

The difference of this code is that while we expect a response from fs.readFile.sync (), the application quietly continues to perform other operations.

Error processing


Anyone who has ever tried to write something more serious on nodejs than the “hello world app” is probably familiar with the error handling routine. If you follow the official design of callback functions, the first argument should always return an error. At the same time, using throw in an asynchronous environment is fraught with the fall of the entire event-loop.

Very real code on nodejs with correct error handling:
 //   -    , //  ,       function asyncFunction(callback) { var p_client = new Db('test', new Server("127.0.0.1", 27017, {})); p_client.open(function(err, p_client) { if (err) return callback(err); // <--  p_client.createCollection('test_custom_key', function(err, collection) { if (err) return callback(err); // <--  collection.insert({'a':1}, function(err, docs) { if (err) return callback(err); // <--  collection.find({'_id':new ObjectID("aaaaaaaaaaaa")}, function(err, cursor) { if (err) return callback(err); // <--  cursor.toArray(function(err, items) { if (err) return callback(err); // <--  //  = items callback(null, items); }); }); }); }); }); } 

The same function, only using sync. The result of its work is identical to the function above, given the error handling. If any of the called functions returns an error in the auto-callback that sync () passes to it, then this error will transparently fall into the resulting callback, which we pointed to the thread with the second argument.
 function syncFunction(callback) { //   Sync(function(){ var p_client = new Db('test', new Server("127.0.0.1", 27017, {})); p_client.open.sync(p_client); var collection = p_client.createCollection.sync(p_client, 'test'); collection.insert.sync(collection, {'a' : 1}); var cursor = collection.find.sync(collection, {'_id':new ObjectID("aaaaaaaaaaaa")) var items = cursor.toArray.sync(cursor); //  = items return items; }, callback) // <--    callback } 

Using the special function Function.prototype.async (), this function can be even simpler (it works similarly to the function above):
 var syncFunction = function() { var p_client = new Db('test', new Server("127.0.0.1", 27017, {})); p_client.open.sync(p_client); var collection = p_client.createCollection.sync(p_client, 'test'); collection.insert.sync(collection, {'a' : 1}); var cursor = collection.find.sync(collection, {'_id':new ObjectID("aaaaaaaaaaaa")) var items = cursor.toArray.sync(cursor); //  = items return items; }.async() // <--          


Parallelism


Sometimes, we need to perform several functions in parallel, while waiting for all the results, and only then continue. For this, there is Sync.Parallel:

 var Sync = require('sync'); // -  ,     function someAsyncFunction(a, b, callback) { setTimeout(function(){ callback(null, a + b); }, 1000) } //   Sync(function(){ //       //      ,       var results = Sync.Parallel(function(callback){ someAsyncFunction(2, 2, callback()); someAsyncFunction(5, 5, callback()); }); console.log(results); // [ 4, 10 ] //   var results = Sync.Parallel(function(callback){ someAsyncFunction(2, 2, callback('foo')); // assign the result to 'foo' someAsyncFunction(5, 5, callback('bar')); // assign the result to 'bar' }); console.log(results); // { foo: 4, bar: 10 } }) 

The other day I spoke with Mr. Laverdet (the creator of node-fibers for v8), and he proposed a very interesting paradigm for the “future”. I added a new method Function.prototype.future () - it can also be used for parallelism:

 //   Sync(function(){ //  someAsyncFunction,     var foo = someAsyncFunction.future(null, 2, 2); var bar = someAsyncFunction.future(null, 5, 5); // foo, bar -     console.log(foo); // { [Function: Future] result: [Getter], error: [Getter] } //   ,    foo  bar console.log(foo.result, bar.result); // 4 10 -    ( ) }) 


Installation


$ npm install sync
$ node-fibers my_file.js


Keep in mind that to support fibers you need to use the “node-fibers” script instead of the “node”.

API


 var Sync = require('sync'); //  , fn - -,    Sync(fn) //  , fn - -, /   callback Sync(fn, callback) //    callback() Sync.Parallel(function(callback){ callback() //   ( ) callback('foo') //    }) //      / // obj - ,        Function.prototype.sync(obj, arg1, arg2) //       ,     //  / Future,    getter 'result' //    Future.result,      ,      // obj - ,        Function.prototype.future(obj, arg1, arg2) //      -  //  ,     // obj -  Function.prototype.async(obj) 


Summary


I intend to continue to develop this direction in nodejs, because it seems to me very correct. I would be glad if one of you is inspired by this idea and contributes to its development.

I advise you to look at quite detailed examples of using the library.
If you intend to participate in the development - fork, welcome, just do not forget about the tests .
I also added the script to the benchmarks . If someone has more ideas on how to test the speed of the fibers, it will be cool.

I want to thank egorF for brainstorming, and for having generally infected me with the fibers theme :)

You may also be interested in other libraries based on node-fibers.

Source: https://habr.com/ru/post/116124/


All Articles