Async control flow "library"

Posted on
  • Used to work with node.js Async library and like this library.
    Having to do lot of asynchronous (so with callback) chaining functions in my project,
    such library might miss in Espruino.
    Example of async.js library transfered :

    function async() {  
      var only_once = function(fn) {
        var called = false;
        return function() {
          if (called) throw new Error("Callback was already called.");
          called = true;
          fn.apply(root, arguments);
        };
      };  
      
      var doSeries = function (fn) {
        return function () {
          var args = Array.prototype.slice.call(arguments);
          return fn.apply(null, [async.eachSeries].concat(args));
        };
      };  
    
      var eachSeries = function (arr, iterator, callback) {
        callback = callback || function () {};
        if (!arr.length) {
          return callback();
        }
        var completed = 0;
        var iterate = function () {
          iterator(arr[completed], function (err) {
            if (err) {
              callback(err);
              callback = function () {};
            }
            else {
              completed += 1;
              if (completed >= arr.length) {
                callback(null);
              }
              else {
                iterate();
              }
            }
          });
        };
        iterate();
      };
      
      var mapSeries = doSeries(_asyncMap);
      
      this.series = function (tasks, callback) {
        callback = callback || function () {};
        if (tasks.constructor === Array) {
          async.mapSeries(tasks, function (fn, callback) {
            if (fn) {
              fn(function (err) {
                var args = Array.prototype.slice.call(arguments, 1);
                if (args.length <= 1) {
                  args = args[0];
                }
                callback.call(null, err, args);
              });
            }
          }, callback);
        }
      };
    }
    
    var async = new async();
    
    async.series([
        function(done) {
          getImage(function(err, result) {
            //some logic)
            done(err, result); 
          });
        },
        function(done) {
          image2Tz(function(err, result) {
            //some ogic
            done(err, result);
          });      
        },
        function(done) {
          fingerFastSearch(function(err, result) {
            //some logic
            done(err, result);
          });
        }
      ],
      function(err, result) {
        if(err !== null) {
          console.log('Error : ' + err);
          //do logic
          return;
        }
                   
        console.log(result);
      }
    );
    

    If something similar is already present in Espruino, let me know...
    Any question or feedback, please let me know...
    I planned to use functions around control flow for the moment.

    [EDITED]
    PS : Sorry.... :(
    the code is not working so well..... I'll update it once fully working

  • Thanks! It could make a good module.

    Does the code you have work in normal JavaScript? If so, and you find out why it doesn't work in Espruino, please can you let me know?

  • Hi Gordon,
    I used jsfiddle.net in Google Chrome (for the console)
    to test and compare the original code of async library.
    Here is the modified code with comments of portion of code that do not react the same in Espruino. I do not know what should work or not in Espruino, what should be standard or not, but async.js is normally using standard javascript (I think :) ).

    function async() {
    
      var eachSeries = function (arr, iterator, callback) {
    
        //Commented next statement as it fails. callback is always assigned to function () {};
        //even if callback has an value.
        
        //callback = callback || function () {};
    
        if (!arr.length) {
          return callback();
        }
        var completed = 0;
        var iterate = function () {
          iterator(arr[completed], function (err) {
            if (err) {
              callback(err);
              callback = function () {};
            }
            else {
              completed += 1;
              if (completed >= arr.length) {
                callback(null);
              }
              else {
                iterate();
              }
            }
          });
        };
        iterate();
      };
    
      var _map = function (arr, iterator) {
        //commented if(arr.map)fails and never enter the if even if arr has a value
        //if (arr.map) {
          return arr.map(iterator);
        //}
        //this is th original code when previous if is not commented
        //var results = [];
        //_each(arr, function (x, i, a) {
        //  results.push(iterator(x, i, a));
        //});
        //return results;
      };
    
      var _asyncMap = function (eachfn, arr, iterator, callback) {
        var results = [];
        arr = _map(arr, function (x, i) {
          return {index: i, value: x};
        });
        eachfn(arr, function (x, callback) {
          iterator(x.value, function (err, v) {
            results[x.index] = v;
            callback(err);
          });
        }, function (err) {
          callback(err, results);
        });
      };
    
      var doSeries = function (fn) {
        //original code
        //return function () {
        //   var args = Array.prototype.slice.call(arguments);
        //   return fn.apply(null, [async.eachSeries].concat(args));
        //};
    
        return function () {
          //arguments.slice replace Array.prototype.slice.call
          var args = arguments.slice(0);
    
          //the following statements replace [eachSeries].concat(args)
          //as .concat is not recognized
          var r = [eachSeries];
    
          for(var i in args) {
            //i should be the value of args and not an integer 
            //i is equal to 0,1,2 (args has 3 items) and normally should be 
            //the value of args[i], isn't it ???
            r.push(args[i]);
          }
          return fn.apply(null, r);
        };
      };
    
      var mapSeries = doSeries(_asyncMap);
    
      this.series = function (tasks, callback) {
    
        //even if callback has a value, the following line fails
        //callback = callback || function () {};
    
        mapSeries(tasks, function (fn, callback) {
    
          // statement if(fn) fails even if fn has a value, so updated it to if (fn !=== null)
          if (fn !== null) {
              fn(function (err) {
                var args = arguments.slice(1);
                if (args.length <= 1) {
                  args = args[0];
                }
                callback.call(null, err, args);
              });
          }
        }, callback);
      };
    
    }
    
  • Using the library is working like this :

    var ac = new async();
    
    console.log('start');
    
    ac.series([
        function(done) {
          console.log('func1 called');
          done(null);
        }
      ],
      function(err, result) {
        console.log('end');
      }
    );
    

    but fails like this

    var ac = new async();
    
    console.log('start');
    
    ac.series([
        function(done) {
          console.log('func1 called');
          done(null);
        },
        function(done) {
          console.log('func2 called');
          done(null);
        }
      ],
      function(err, result) {
        console.log('end');
      }
    );
    

    En error message from Espruino says that it reached the stack limitation for recursivity functions.
    I tried to add a setTimeout(fn(function (err) ...., 0) in this.series to 'empty' the stack.
    I used this trick in some of my code to create recursivity :
    using setTimeout allow the stack to continue the program (and do not fill it) and then call the recursive function. But even with this trick this is not wokring.

    Even if I can make the code working, I'm afraid the board has not enough 'power' or memory.

    What do you think ?

  • Hmm. Well, it won't like having lots of recursion... setTimeout is a great way to avoid it though. The code looks way more complicated than is really needed to do what's required - really, what you're trying to do should work fine in Espruino - it just seems that the async library does some crazy stuff.

    For instance unless I'm misunderstanding, what you're doing could actually be handled using:

    function series(arr,done) {
      var i=0;
      function next(err) {
        if (((err!==undefined) && (err!==null)) || i>=arr.length)
          done(err);
        else
          arr[i++](next);
      }
      next();
    }
    

    (although obviously that would quickly exhaust the stack - although it could be changed to use setTimeout pretty easily)

    The actual bugs with Espruino seem to be:

    • function(){} returns false when cast to a boolean (it should return true)
    • no Array.concat
    • You can't reference built-in functions (this is a big one that I'm planning to fix - but it requires a lot of work)

    I'll see if I can fix the first 2 for 1v59 - the other one's going to need a bit more work :)

    By the way, for (... in ...) works correctly AFAIK. This is the result from jsconsole:

    for (var i in ["a","b","c"]) console.log(i)link
    "0"
    "1"
    "2"
    

    Which is what Espruino does.

  • I agree that async.js might be a bit heavy for the simple fact of doing series :)
    But this library do a lot more and I had in mine to port this library to have something similar in Espruino, by also adding then the doWhile, etc etc with asynchronous management.... but you are right :)

    Thanks for fixing the first two points.
    Is the first point fixing the line :
    ° callback = callback || function () {};
    ° if (fn)
    ° if(arr.map)

    But for me the for(.... in ... ) is also "wrong" ? In the code I'm ususally do it used to return the value (the item) and not the index (as an integer) in the array.
    what I except is

        for (var i in ["a","b","c"]) console.log(i)link
        "a"
        "b"
        "c"
    

    Is Espruino "standard" javascript ? or is it mine code that is not standard :) ?
    I have to admit that I have worked more with node.js so I might be wrong in some of my comments

  • Yes, the first point will fix all 3 of those lines :)

    I think your code is the non-standard one - [click here to try it out on jsconsole and see what happens](http://www.jsconsole.com/?for%20(var%20i­%20in%20%5B%22a%22%2C%22b%22%2C%22c%22%5­D%29%20console.log(i%29)

  • Regarding:

    for (var i in ["a","b","c"]) console.log(i)
    "a"
    "b"
    "c"
    

    This is incorrect. for-in will assign the value of the "key" (in this case the numeric index) to i, not the property "value".

    This is correct:

    for (var i in ["a","b","c"]) console.log(i)
    0
    1
    2
    
  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Async control flow "library"

Posted by Avatar for ChrisB @ChrisB

Actions