asynchrony-di

0.1.0-3 • Public • Published

Synopsis

An unopinionated asynchronous Dependency Injector and Orchestrator.

The need for another Dependency Injection Framework

Asynchrony DI was created at StackRoute (Github) to solve the problem of asynchronous dependency injection and orchestration of extensible counters in it's Quiz Framework, for awarding badges to quiz players in real-time. For instance, when the player wins his 10th quiz in a streak, we could award him a "Super Streak" badge. The challenges were as follows:

  1. Counters ("Winning Streak") and badges ("Super Streak"), are extensible. This means, new counters and badges could be added to the platform, and counters that have to be evaluated for any given event may increase.
  2. Counters need to be evaluated only once. A cached value needs to be returned when it is depended upon subsequently.
  3. To award the badges to the player in real-time, they need to be evaluated with the highest priority, which in turn require that their dependent counters be evaluated. This requires an IOC / DI framework.
  4. Counter values are stored in a shared In-Memory store, which can be retrieved with an async call.

As such, we first evaluated wagner-core, orchestrator, and Angular's di. Wagner and Angular's di are designed to bootstrap functions as dependencies, which are called during runtime in a synchronous fashion. This would not allow for returning and caching of asynchronous data. Wagner's synchronous approach would not let us retrieve counter values from an external data store. It's asynchronous dependencies, on the other hand, does not cache responses for re-use. Orchestrator is good at orchestrating and running dependencies in the right order, but lacks the ability to inject asynchronous cached values. Hence, we created Asynchrony-DI package, which would cache values retrieved in an asynchronous manner, and also orchestrate dependency execution.

Code Example

Installation

use npm to install the asynchrony-di module.

npm install asynchrony-di --save

Get a reference:

var Asynchrony = require('asynchrony-di');
var asynchrony = new Asynchrony();

Load it up with stuff to retrieve:

asynchrony.add('thing1',[function(done){
  setTimeout(function() {
    done(null, 'foo')
  });
}]);
 
asynchrony.add('thing2', [function(done){
  setTimeout(function() {
    done(null, 'bar');
  });
}]);

These dependencies are not evaluated until depended upon.

Run the tasks:

asynchrony.invoke(['thing1', 'thing2', function (t1, t2) {
  console.log(t1); // prints foo;
  console.log(t2); // prints bar;
}]);

API Reference

Add a task

asynchrony.add(name,[deps,fn]);

Parameter Type Description

name : String The name of the task.

deps : Task names to be executed before running the given task, it is optional.

fn : Function The actual function that gets executed when the given task is invoked.
Note: Take in a callback (done) for Async tasks

Example:

async_di.add('FirstName',[function(done){
    return done(null,'foo');
  }]);
async_di.add('SecondName',[function(done){
  return done(null,'bar')
}]);
async_di.add('FullName', ['FirstName','LastName', function(firstName, lastName, done) {
  //Form full name from the dependency values
    return done(null,firstName+" "+lastName);
}]);

Invoke

asynchrony.invoke(names[, fn(,value)]);

Execute a task

asynchrony.invoke(['thing1', function(t1){
  // do stuff
}]);
Parameter Type Description

names - String The names of the tasks to be invoked
fn - Function The callback function which will be executed after all the mentioned tasks are complete.
value - Object Possess the return values from the invoked tasks

Example

asynchrony.add('FullName', ['FirstName','LastName', function(firstName, lastName, done) {
  //Form full name from the dependency values
    return done(null,firstName+lastName);
}]);
 
asynchrony.invoke([‘FullName’, function(fullName){
  //Perform something after getting full name
  console.log(fullName);
}]);

Invoke Remaining Dependencies

asynchrony.invokeRemainingTask (fn);

Execute tasks which weren’t explicitly invoked

asynchrony.invokeRemainingTask (function(){
  // do something after all the remaining tasks are completed
});

Parameter Type Description

fn Function - The callback function which will be executed after all the remaining tasks are complete.

Example

asynchrony.add('FirstName',[function(done){
  // return done(null,firstName);
}]);
asynchrony.add('SecondName',[function(done){
  // return done(null,secondName)
}]);
asynchrony.invoke( [‘FirstName’, function(firstName) {
  //do something with firstname
}]);
asynchrony.invokeRemainingTask(function(){
  //do something after lastname is also executed
});

Tests

Asynchronous dependency injection test

var Asynchrony = require('asynchrony-di');
var should = require('should');
describe('Async Dependency Injection', function() {
  it('Basic Scenario for async test', function(done) {
    var asynchrony = new Asynchrony();
 
    var rand = (Math.random()*99387593793);
    var count = 0;
 
    asynchrony.add('thing1', [function(done) {
      setTimeout(function() {
        count++;
        return done(null, rand);
      },1000);
    }]);
 
    count.should.be.exactly(0);
 
    asynchrony.invoke(['thing1', function(thing1) {
      count.should.be.exactly(1);
      thing1.should.be.exactly(rand);
      done();
    }]);
  });
});

Multiple Dependencies Invocation

var Asynchrony = require('asynchrony-di');
var should = require('should');
describe('Async Dependency Injection', function() {
  it('Multiple Dependencies Invocation', function(done) {
    var asynchrony = new Asynchrony();
 
    var rand1 = (Math.random()*958728493827);
    var rand2 = (Math.random()*958728493827);
    var count1 = 0;
    var count2 = 0;
 
    asynchrony.add('thing1', [function(done) {
      setTimeout(function() {
        count1++;
        return done(null, rand1);
      }, 1000);
    }]);
 
    asynchrony.add('thing2', [function(done) {
      setTimeout(function() {
        count2++;
        return done(null, rand2);
      });
    }]);
 
    count1.should.be.exactly(0);
    count2.should.be.exactly(0);
 
    asynchrony.invoke(['thing1', 'thing2', function(t1, t2) {
      t1.should.be.exactly(rand1);
      t2.should.be.exactly(rand2);
      count1.should.be.exactly(1);
      count2.should.be.exactly(1);
      done();
    }]);
  });
});

Nested Dependency Invocation

var Asynchrony = require('asynchrony-di');
var should = require('should');
describe('Async Dependency Injection', function() {
  it('Nested Dependency Invocation', function(done) {
    var asynchrony = new Asynchrony();
 
    var rand1 = Math.random() * 1837953092;
    var rand2 = Math.random() * 5849382729;
 
    var count1 = 0;
    var count2 = 0;
 
    asynchrony.add('thing1', [function(done) {
      setTimeout(function() {
        count1++;
        return done(null, rand1);
      }, 300);
    }]);
 
    asynchrony.add('thing2', ['thing1', function(t1, done) {
      setTimeout(function() {
        count2++;
        return done(null, t1+rand2);
      },1000);
    }]);
 
    count1.should.be.exactly(0);
    count2.should.be.exactly(0);
 
    asynchrony.invoke(['thing2', function(t1) {
      t1.should.be.exactly(rand1 + rand2);
      count1.should.be.exactly(1);
      count2.should.be.exactly(1);
      done();
    }]);
  });
});

Extra Nested Multiple Dependencies

var Asynchrony = require('asynchrony-di');
var should = require('should');
describe('Async Dependency Injection', function() {
  it('Extra Nested Multiple Dependencies', function(done) {
    var asynchrony = new Asynchrony();
 
    var rand1 = Math.random() * 1837953092;
    var rand2 = Math.random() * 5849382729;
    var rand3 = Math.random() * 5838308403;
 
    var count1 = 0;
    var count2 = 0;
    var count3 = 0;
 
    asynchrony.add('thing1', [function(done) {
      setTimeout(function() {
        count1++;
        return done(null, rand1);
      }, 300);
    }]);
 
    asynchrony.add('thing2', ['thing1', function(t1, done) {
      setTimeout(function() {
        count2++;
        return done(null, t1+rand2);
      },400);
    }]);
 
    asynchrony.add('thing3', ['thing1', 'thing2', function(t1,t2, done) {
      setTimeout(function() {
        count3++;
        return done(null, t1+t2+rand3);
      }, 100);
    }]);
 
    count1.should.be.exactly(0);
    count2.should.be.exactly(0);
    count3.should.be.exactly(0);
 
    asynchrony.invoke(['thing3', 'thing2', 'thing1', function(t3,t2,t1) {
      t1.should.be.exactly(rand1);
      t2.should.be.exactly(rand1+rand2);
      t3.should.be.exactly(2*rand1+rand2+rand3);
      count1.should.be.exactly(1);
      count2.should.be.exactly(1);
      count3.should.be.exactly(1);
      done();
    }]);
  });
});

Remaining Task Execution

var Asynchrony = require('asynchrony-di');
var should = require('should');
describe('Async Dependency Injection', function() {
  it('Remaining Task Execution Test', function(done) {
    var asynchrony = new Asynchrony();
 
    var rand1 = (Math.random()*99387593793);
    var rand2 = (Math.random()*67674367647);
    var rand3 = (Math.random()*19928738278);
    var count1 = 0;
    var count2 = 0;
    var count3 = 0;
 
    asynchrony.add('thing1', [function(done) {
      setTimeout(function() {
        count1++;
        return done(null, rand1);
      },200);
    }]);
    asynchrony.add('thing2', [function(done) {
      setTimeout(function() {
        count2++;
        return done(null, rand2);
      },300);
    }]);
    asynchrony.add('thing3', [function(done) {
      setTimeout(function() {
        count3++;
        return done(null, rand3);
      },100);
    }]);
    count1.should.be.exactly(0);
    count2.should.be.exactly(0);
    count3.should.be.exactly(0);
    asynchrony.invoke(['thing1', function(thing1) {
      count1.should.be.exactly(1);
      count2.should.be.exactly(0);
      count3.should.be.exactly(0);
      thing1.should.be.exactly(rand1);
      asynchrony.invokeRemainingTask(function(err,data){
        //data is an object(key:value) which has Remaining dependency value. Key is the name of the dependency and value which will be equal to returned value of the invoked dependency.
        data['thing2'].should.be.exactly(rand2);
        data['thing3'].should.be.exactly(rand3);
        count1.should.be.exactly(1);
        count2.should.be.exactly(1);
        count3.should.be.exactly(1);
        done();
      });
    }]);
  });
});

License

MIT

Package Sidebar

Install

npm i asynchrony-di

Weekly Downloads

2

Version

0.1.0-3

License

MIT

Last publish

Collaborators

  • kirushna
  • rabbani