Code Monkey home page Code Monkey logo

bach's Introduction

bach

NPM version Downloads Build Status Coveralls Status

Compose your async functions with elegance.

Usage

With bach, it is very easy to compose async functions to run in series or parallel.

var bach = require('bach');

function fn1(cb) {
  cb(null, 1);
}

function fn2(cb) {
  cb(null, 2);
}

function fn3(cb) {
  cb(null, 3);
}

var seriesFn = bach.series(fn1, fn2, fn3);
// fn1, fn2, and fn3 will be run in series
seriesFn(function (err, res) {
  if (err) {
    // in this example, err is undefined
    // handle error
  }
  // handle results
  // in this example, res is [1, 2, 3]
});

var parallelFn = bach.parallel(fn1, fn2, fn3);
// fn1, fn2, and fn3 will be run in parallel
parallelFn(function (err, res) {
  if (err) {
    // in this example, err is undefined
    // handle error
  }
  // handle results
  // in this example, res is [1, 2, 3]
});

Since the composer functions return a function, you can combine them.

var combinedFn = bach.series(fn1, bach.parallel(fn2, fn3));
// fn1 will be executed before fn2 and fn3 are run in parallel
combinedFn(function (err, res) {
  if (err) {
    // in this example, err is undefined
    // handle error
  }
  // handle results
  // in this example, res is [1, [2, 3]]
});

Functions are called with async-done, so you can return a stream, promise, observable or child process. See async-done completion and error resolution for more detail.

// streams
var fs = require('fs');

function streamFn1() {
  return fs
    .createReadStream('./example')
    .pipe(fs.createWriteStream('./example'));
}

function streamFn2() {
  return fs
    .createReadStream('./example')
    .pipe(fs.createWriteStream('./example'));
}

var parallelStreams = bach.parallel(streamFn1, streamFn2);
parallelStreams(function (err) {
  if (err) {
    // in this example, err is undefined
    // handle error
  }
  // all streams have emitted an 'end' or 'close' event
});
// promises
function promiseFn1() {
  return Promise.resolve(1);
}

function promiseFn2() {
  return Promise.resolve(2);
}

var parallelPromises = bach.parallel(promiseFn1, promiseFn2);
parallelPromises(function (err, res) {
  if (err) {
    // in this example, err is undefined
    // handle error
  }
  // handle results
  // in this example, res is [1, 2]
});

All errors are caught in a domain and passed to the final callback as the first argument.

function success(cb) {
  setTimeout(function () {
    cb(null, 1);
  }, 500);
}

function error() {
  throw new Error('Thrown Error');
}

var errorThrownFn = bach.parallel(error, success);
errorThrownFn(function (err, res) {
  if (err) {
    // handle error
    // in this example, err is an error caught by the domain
  }
  // handle results
  // in this example, res is [undefined]
});

When an error happens in a parallel composition, the callback will be called as soon as the error happens. If you want to continue on error and wait until all functions have finished before calling the callback, use settleSeries or settleParallel.

function success(cb) {
  setTimeout(function () {
    cb(null, 1);
  }, 500);
}

function error(cb) {
  cb(new Error('Async Error'));
}

var parallelSettlingFn = bach.settleParallel(success, error);
parallelSettlingFn(function (err, res) {
  // all functions have finished executing
  if (err) {
    // handle error
    // in this example, err is an error passed to the callback
  }
  // handle results
  // in this example, res is [1]
});

API

series(fns..., [options])

Takes a variable amount of functions (fns) to be called in series when the returned function is called. Optionally, takes an options object as the last argument.

Returns an invoker(cb) function to be called to start the serial execution. The invoker function takes a callback (cb) with the function(error, results) signature.

If all functions complete successfully, the callback function will be called with all results as the second argument.

If an error occurs, execution will stop and the error will be passed to the callback function as the first parameter. The error parameter will always be a single error.

parallel(fns..., [options])

Takes a variable amount of functions (fns) to be called in parallel when the returned function is called. Optionally, takes an options object as the last argument.

Returns an invoker(cb) function to be called to start the parallel execution. The invoker function takes a callback (cb) with the function(error, results) signature.

If all functions complete successfully, the callback function will be called with all results as the second argument.

If an error occurs, the callback function will be called with the error as the first parameter. Any async functions that have not completed, will still complete, but their results will not be available. The error parameter will always be a single error.

settleSeries(fns..., [options])

Takes a variable amount of functions (fns) to be called in series when the returned function is called. Optionally, takes an options object as the last argument.

Returns an invoker(cb) function to be called to start the serial execution. The invoker function takes a callback (cb) with the function(error, results) signature.

All functions will always be called and the callback will receive all settled errors and results. If any errors occur, the error parameter will be an array of errors.

settleParallel(fns..., [options])

Takes a variable amount of functions (fns) to be called in parallel when the returned function is called. Optionally, takes an options object as the last argument.

Returns an invoker(cb) function to be called to start the parallel execution. The invoker function takes a callback (cb) with the function(error, results) signature.

All functions will always be called and the callback will receive all settled errors and results. If any errors occur, the error parameter will be an array of errors.

options

The options object is primarily used for specifying functions that give insight into the lifecycle of each function call. The possible extension points are create, before, after and error. If an extension point is not specified, it defaults to a no-op function.

The options object for parallel and settleParallel also allows specifying concurrency in which to run your functions. By default, your functions will run at maximum concurrency.

options.concurrency

Limits the amount of functions allowed to run at a given time.

options.create(fn, index)

Called at the very beginning of each function call with the function (fn) being executed and the index from the array/arguments. If create returns a value (storage), it is passed to the before, after and error extension points.

If a value is not returned, an empty object is used as storage for each other extension point.

This is useful for tracking information across an iteration.

options.before(storage)

Called immediately before each function call with the storage value returned from the create extension point.

options.after(result, storage)

Called immediately after each function call with the result of the function and the storage value returned from the create extension point.

options.error(error, storage)

Called immediately after a failed function call with the error of the function and the storage value returned from the create extension point.

License

MIT

bach's People

Contributors

aashna956 avatar bnjmnt4n avatar ckross01 avatar erikkemperman avatar github-actions[bot] avatar jpeer264 avatar pdehaan avatar per2plex avatar phated avatar pkozlowski-opensource avatar sttk avatar trysound avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

bach's Issues

Using objects / passing keys down to now-and-later

The code in master uses now-and-later where one can pass either an array or an object with functions to execute but it seems that there is no way to pass an object down to now-and-later.

If I understand your intentions correctly passing objects was a way to "name" anonymous functions so if someone uses tasks being anonymous functions in Gulp4 they would still get meaningful timing info. I was imagining that orchestrator would build an objects with functions to execute, "naming" those functions using task names, function names or a generated id (as the last resort). If so we need to find a way of passing down an object with those functions through bach.

Just to make the discussion more concrete, here is the use-case I'm having on my mind:

gulp = require('gulp4');


gulp.task(function foo(done) {...});

gulp.task('bar', function (done) {...});

var baz = function (done) {...});

gulp.task('default', ['foo', 'bar', baz]);

For the foo function we can easily get a name / key from the function name itself, for bar we don't have it so would use a task name and for baz we've got nothing so would have to generate a key.

Now, those names / ids are important to properly report timing / duration of each task and I was under the impression that this is what an object-as-argument was introduced in now-and-later. If so we need to find a way of passing this info down to now-and-later. Honestly I'm not sure what would be a nice API here, something like this maybe, perhaps: bach.parallel(foo, {bar: bar})? Or bach.parallel({foo: foo, bar: bar})?

I must admit that I'm still not at ease with new layering so I might be talking rubbish here. In any case the functional use-case I'm after here is timing tasks in Gulp4. Thoughts?

somehow to pass context

To the each function? Actually it would be same for all in stack.

function fn1 (cb) {
  assert.equal(this.hello, 'world')
  this.foo = 'foo'
  cb(null, 1)
}

function fn2 (cb) {
  assert.equal(this.foo, 'foo')
  assert.equal(this.hello, 'world')
  this.bar = 'bar'
  cb(null, 2)
}

function fn3 (cb) {
  assert.equal(this.bar, 'bar')
  assert.equal(this.hello, 'world')
  cb(null, 3)
}

var seriesDone = bach.series(fn1, fn2, fn3)
seriesDone({hello: 'world'}, function (err, res) {
  console.log(res)
  //=> [1, 2, 3]
})

like middleware/plugin stack

Coveralls Badge unknown

On bach's coveralls, there is no master branch - so the badge will link to an unknown branch. You may have to sync the branches in coveralls to get a correct badge again.

Verify arguments to parallel / series

Passing a non-function (ex. undefined) to series / parallel results in an "obscure" error from underlying libraries. Arguments supplied to parallel / series should probably be verified and an explicit error should be thrown if any of the arguments is not a function.

Happy to send a PR if this makes sense.

series returns too soon if sync function is first

If you have a sync function and an async function, and pass the sync function to .series first, then execution finishes before the async function is finished:

var bach = require('bach');
function sync () {
  console.log('sync function');
}

function async (done) {
  console.log('async function');
  done(null, 'async done');
}

var fn = bach.series(sync, async);
fn(function (err, msg) {
  console.log(msg);
});

This could be handled in async-done by checking the function length (like was done in orchestrator), but I don't know if that's how you're intending async-done to be used, so I opened the issue here.

cleanup package.json

I want all the gulp4 repos to have the same structure in package.json. It also needs things like engine that specifies we only work in node 0.10

Update docs

The docs mention that settle{Series,Parallel} can be accessed on the bach object, but AFAIK they are only accessible via require("bach/settle"). Perhaps you should update the docs about this.

Update docs

Docs are out of sync with the newest now-and-later. Need to update.

better .jshint

I want to cleanup the jshint and add a linting task in the project. Probably not a good idea to depend on gulp to lint.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.