Giter Site home page Giter Site logo

runtime's Introduction

runtime NPM versiondownloads

Build status

breaking changes - documentation - examples - install - todo - why

The aim of the project is to compose asynchronous functions and provide a basic api to create an interface around them. It is for people who hate so many choices around the same problem (i.e. callbacks, promises, streams, ...)

Once these asynchronous functions are composed, they are not executed right away. Instead another function is returned leaving execution of this stack to the writer. This function can be used multiple times.

Note that every function is made asynchronous and should be resolved either with a callback, returning a stream, a promise or a RxJS observable.

usage

As an example let's make 3 async functions. One using a callback, other returning a promise and another a stream.

var fs = require('fs');
var through = require('through2');
var Promise = require('es6-promise').Promise;

function foo (next, value) {
  console.log('received `%s`', value);
  setTimeout(function () {
    next(null, 'Callback');
  }, Math.random() * 10);
}

function bar (next, value) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(value + 'Promise');
    }, Math.random() * 10);
  });
}

function baz (next, value) {
  var stream = fs.createReadStream(__filename);

  return stream.once('end', function () {
    next(null, value + 'Stream');
  });
}

All right we have 3 functions. Lets setup an interface around them. For the sake of simplicity lets make a logger with error handling.

var Runtime = require('runtime');

// create a composer class that will have what we need
var Composer = Runtime.createClass({
  reduceStack: function (stack, site) {
    if(typeof site === 'function'){
      stack.push({
        fn: site,
        label: Array.isArray(site.stack)
          ? this.tree(site.stack).label
          : site.label || site.name || 'anonymous'
      });
    }
  },
  onHandleStart: function (site, stack) {
    console.log('`%s` started', site.label);
    site.time = process.hrtime();
  },
  onHandleEnd: function (site, stack) {
    var diff = process.hrtime(site.time);
    console.log('`%s` ended after %s ms',
      site.label, diff[0]*1e+3 + Math.floor(diff[1]*1e-6)
    );
  },
  onHandleError: function (error, site) {
    var file = error.stack.match(/\/[^)]+/).pop();
    console.log('`%s` errored at', site.label, file);
    console.log(error.stack);
  }
});

Now let's compose those into one asynchronous function using this brand new runtime instance we have created.

How does it look like?

The default goes like this: last argument for options, all the others for functions.

// create a Composer instance
var runtime = Composer.create();

var composed = runtime.stack(foo, bar, baz, { wait: true });
// runtime.stack will run each site in parallel by default
// to change it pass `{ wait: true }` and each site will run in series

// lets make it pretty
console.log('Stack tree -> %s',
  require('archy')(runtime.tree(composed.stack))
);

// use it just as normal async function
composed('insert args here', function done(error, result){
  if (error) {
    console.log(err.stack);
  } else {
    console.log('result: `%s`', result);
  }
});

Here we go. This is the output logged.

Stack tree -> foo, bar, baz
├── foo
├── bar
└── baz

`foo` started
received `insert args here`
`foo` ended after 3 ms
`bar` started
`bar` ended after 3 ms
`baz` started
`baz` ended after 7 ms
result: `CallbackPromiseStream`

documentation

Work in progress.

install

With npm

npm install --save runtime

breaking changes

If you where using a previous version, the internals have been cleaned and simplified a lot to offer the same idea with less opinions and more reuse.

Now runtime.stack composes only functions by default. If you want to give strings that then are mapped to a function, that is, you want to write

var composed = runtime.stack('foo', 'bar');

you will have to use the following approach

var Runtime = require('runtime');

// create a class
var RuntimeClass = Runtime.createClass({
  create: function () {
    this.tasks = {};
  },
  task: function (name, handle) {
    if (typeof name !== 'string') {
      throw new TypeError('`name` should be a string');
    } else if (typeof handle !== 'function') {
      throw new TypeError('`handle` should be a function');
    }

    this.tasks[name] = handle;
    return this;
  },
  // similar to Array.prototype.reduce with an empty array
  // given for the for the previous argument (stack = [] on first call)
  reduceStack: function (stack, site) {
    if (typeof site === 'string' && typeof this.tasks[site] === 'function') {
      stack.push(this.tasks[site]);
    } else if (typeof site === 'function') {
      stack.push(site);
    }
  }
});

// instantiate
var runtime = RuntimeClass.create();

// register your mapping from string to function
runtime.task('one', function handleOne (next, myArg) {
  // do async things
  next(); // or return a  promise, stream or RxJS observable
});

function two (next, myArg) {
  // do async things
  next(); // or return a  promise, stream or RxJS observable
}

// now you can `stack` functions and strings together
var composer = runtime.stack('one', two);

// run the `stack` function returned
composer('myArg', function onStackEnd (err, result) {
  if (err) { throw err; }
  console.log(result);
});

test

➜  runtime (master) ✔ npm test

api
  ✓ onHandleStart is called before each site
  ✓ onHandleEnd is called before each site
  ✓ nested: onHandleStart is called before and after each site
  ✓ nested: onHandleEnd is called before and after each site
  ✓ context for each stack can be given {context: [Object]}
  ✓ can be reused with no side-effects
  ✓ create({wait: true}) makes all stacks wait

exports
  ✓ create() should return a new instance
  ✓ create(object mixin) should add to the instance properties
  ✓ createClass() should return a new constructor
  ✓ createClass(object mixin) mixin with new constructor
  ✓ createClass({create: [Function]}) should be used as ctor

stack-callbacks
  ✓ uses the callback when a fn throws
  ✓ uses the callback when passes the error
  ✓ passes error to onHandleError when no callback given
  ✓ runs the callback on completion
  ✓ runs fns in parallel by default
  ✓ {wait: true} should run functions in series
  ✓ passes arguments when fns wait
  ✓ does NOT pass arguments when fns does NOT wait

stack-promises
  ✓ uses the callback when a promise throws
  ✓ uses the callback when promises rejects
  ✓ passes error to onHandleError if no callback was given
  ✓ runs the callback after completion of all promises
  ✓ runs in parallel by default
  ✓ runs in series with {wait: true}
  ✓ passes arguments when it waits
  ✓ does NOT pass arguments when fns does NOT wait

stack-streams
  ✓ uses the callback when a stream throws an error
  ✓ uses the callback when a stream emits an error
  ✓ passes error to onHandleError if no callback was given
  ✓ runs the callback after completion of all streams
  ✓ runs in parallel by default
  ✓ runs in series with {wait: true}

stacks-composed
  ✓ runs callback if fn throws from other stack
  ✓ runs callback if error given to next from other stack
  ✓ runs the callback on completion of all stacks
  ✓ runs stacks in parallel by default
  ✓ {wait: true} should run stacks in series
  ✓ series: callback is run after all stacks are finished
  ✓ passes arguments when host and completed stack waits
  ✓ does NOT pass arguments when stacks does NOT wait


42 passing (229ms)

why

There are several ways to manage complexity for asynchronous functions. Ones are better than others for some use-cases and sometimes with callbacks is more than enough. But we all want to avoid callback hell and reuse as much as possible.

todo

  • be able to redo or rewind within the same stack

license

The MIT License (MIT)

Copyright (c) 2015-present Javier Carrillo

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

runtime's People

Contributors

stringparser avatar

Stargazers

luigi avatar Bruno  avatar Rodrigo G. López avatar  avatar

Watchers

 avatar

Forkers

tompop1777

runtime's Issues

No new line onPrompt completion

The prompt is automatically moved to a new line when there are no matches.

Instead of printing all the completion text and go to a new line, it would be better to preserve the cursor position.

Changes on `runtime.require`

Right now, runtime.require loads all files in a folder.

From what is done already here are some thoughts about what could make it better.

  • If the file exported something runtime.require should return it.
  • No antipatterns:
    • index.js should exist for that directory. If it doesn't exists create it.
    • Add a callback to runtime.require the first argument an error and the second the module.exports object of that folder so if necessary something can be done.

make good docs

Start writing some good docs for

  • the runtime api
  • the stack api
  • the next callback
  • use cases like: building a test suite, build system, app, repl, etc.

do not share `stack` with each site

Setup the context by site, do not pass the stack if wasn't specified. Leave a door open in case its needed using site.context to check before assignment.

Sharing the stack object shouldn't be business of the sites but the stack handlers. This keeps things separated.

This basically serves as an use-case for gulp-runtime but I really think is a good separation of concerns here.

Run commands on demand

At the moment is possible to run commands defined. It would be also helpful to consume them on demand based on instance's events or common ground like onStartup event, which is already implemented.

For example, could do

var runtime = require('gulp-runtime');

runtime.onStartup(function(){
  this.run(<command1> + <command2> + ...);
})

At the moment is possible to do

runtime.startup(function(){
   this.setPrompt(' > custom prompt');
   this.prompt()
})

History

I'll be useful to provide a way to recall the history of the last session on the project.

`fs` completion

Provide path completion.

Sniff the directory tree and merge it with the existing completion for that path when it applies.

This should be add at the #root command and inherited downward.

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.