0

I need to perform the equivalent of the async.eachSeries() method, but I did not want to add a dependency such as require('async').

So I came up with the implementation below, and I was wondering if there is a better way to do this?

it 'String::start_Process_Capture_Console_Out', (done)->

    runTest = (testData,next)->
        name          = testData.process_Name
        parameter     = testData.process_Parameter
        expected_Data = testData.expected_Data
        name.start_Process_Capture_Console_Out parameter, (data)->
            data.assert_Is(expected_Data)
            next()

    runTests = (testsData, next)->
        if testsData.empty() 
            next() 
        else 
            runTest testsData.pop(), ()-> runTests(testsData, next)

    testsData = [
                    {process_Name: 'echo' , process_Parameter: 'hello'       , expected_Data:'hello\n' }
                    {process_Name: 'echo' , process_Parameter: ['hello','me'], expected_Data:'hello,me\n' }
                    {process_Name: 'git'  , process_Parameter: ['xyz'       ], expected_Data:'git: \'xyz\' is not a git command. See \'git --help\'.\n' }
                    {process_Name: 'ls'   , process_Parameter: '.'           , expected_Data:'LICENSE\nREADME.md\nindex.js\nnode_modules\npackage.json\nsrc\ntest\n' }
                ]    

    runTests(testsData, done)

For reference here are the start_Process and start_Process_Capture_Console_Out string prototype methods

String::start_Process = (args...)->
  args ?= []
  return child_process.spawn(@.str(),args)


String::start_Process_Redirect_Console = (args...)->
  args ?= []
  childProcess = @.start_Process(args)
  childProcess.stdout.on 'data', (data)->console.log(data.str().trim())
  childProcess.stderr.on 'data', (data)->console.log(data.str().trim())
  return childProcess  

String::start_Process_Capture_Console_Out =  (args... , callback)->
    consoleData = ""
    childProcess = @.start_Process(args)
    childProcess.stdout.on 'data', (data)->consoleData+=data
    childProcess.stderr.on 'data', (data)->consoleData+=data
    childProcess.on 'exit', ()->
      callback(consoleData)
    return childProcess

One solution would be to add a prototype to the Array class, maybe called async_Each_Series so that we could just have:

    testsData = [
                    {process_Name: 'echo' , process_Parameter: 'hello'       , expected_Data:'hello\n' }
                    {process_Name: 'echo' , process_Parameter: ['hello','me'], expected_Data:'hello,me\n' }
                    {process_Name: 'git'  , process_Parameter: ['xyz'       ], expected_Data:'git: \'xyz\' is not a git command. See \'git --help\'.\n' }
                    {process_Name: 'ls'   , process_Parameter: '.'           , expected_Data:'LICENSE\nREADME.md\nindex.js\nnode_modules\npackage.json\nsrc\ntest\n' }
                ]    

   testsData.async_Each_Series(runTest, done)
3
  • It seems to me you have implemented eachSeries, not each? Commented Dec 5, 2014 at 4:09
  • 1
    By what criteria "better"? If you want to focus on mainability and readability, don't reinvent the wheel and just require async. There's no harm in that. Commented Dec 5, 2014 at 4:11
  • @Bergi yes I implemented eachSeries (going to update the post with the fix) Commented Dec 15, 2014 at 11:36

1 Answer 1

1

the nodejs Array class already has a forEach method, but it does not take a callback

[1,2,3,4].forEach (i) ->
    console.log i

if you need the callback, write (js this time)

function applyVisitor( data, visitor, next ) {
    // ...
}

Or you can also avoid dependencies and cut-and-past copy the relevant code into yours.

(Edit: in short, yes, there is a better way -- write a general-purpose iterator and use it for the these tests, do not write special-purpose loop just for this.)

My implementation of applyVisitor (also in https://npmjs.org/package/aflow)

/**
 * Repeatedly call func until it signals stop (returns truthy) or err.
 * Func is passed just a standard callback taking err and ret.
 * Returns via its callback the truthy value from func.
 */
function repeatUntil( func, callback ) {
    'use strict';

    callback = callback || function() {};

    function _loop( func, callback, callDepth ) {
        try {
            func( function(err, stop) {
                if (err || stop) return callback(err, stop);
                if (callDepth < 40) _loop(func, callback, callDepth + 1);
                else setImmediate(function() { _loop(func, callback, 0); });
            } );
        }
        catch (e) { callback(e); }
    }
    // note: 2.5x faster if callback is passed in to _loop vs pulled from closure

    _loop(func, callback, 0);
}


/**
 * visitor pattern: present all items to the visitor function.
 * Returns just error status, no data; to capture the results,
 * wrapper the visitor function.
 */
function applyVisitor( dataItems, visitorFunction, callback ) {
    'use strict';

    if (!Array.isArray(dataItems)) {
        return callback(new Error("expected a data array, but got " + typeof data));
    }

    var next = 0;
    repeatUntil(
        function applyFunc(cb) {
            if (next >= dataItems.length) return cb(null, true);
            visitorFunction(dataItems[next++], function(err) {
                cb(err, err);
            });
        },
        function(err, stop) {
            callback(err);
        }
    );
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.