runtime

    0.14.1 • Public • Published

    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.

    Install

    npm i runtime

    DownloadsWeekly Downloads

    452

    Version

    0.14.1

    License

    MIT

    Last publish

    Collaborators

    • stringparser