decompose
Extracted from deepjs core lib.
Javascript Functions/Aspects Forge.
Functions/Aspects (de)composition tools which produce standard functions usable anywhere in your js.
It is :
- lightweight (3.6 Ko minified, 1.2 Ko minified/gzipped)
- fast
- chainable
- lazzy (re)compiled with compilation cache
- easily customisable
- working browser and server side
- deep.ocm compliant
- absolutly non-obstrusive
- incredibly powerful
It takes benefits from Promise pipelining pattern by :
- gathering function decorator aspects semantic (before, around, after) and Promise specific keywords (done, fail, always).
- managing transparently any promise/thenable returned from composed functions
Inspired from https://github.com/kriszyp/compose aspect's decorators.
Content
- install
- usage
- examples
- API
- arguments management
- as aspects
- custom composers
- Promise compliance
- deep-ocm compliance
- advanced usage
- tests
Install
> git clone https://github.com/nomocas/decompose.git
or
> npm install decompose
or
> bower install decompose
Usage
You could either use it as an AMD module (requirejs, almond, ...) in your browser, or as a CommonJS module under nodejs, or by including the file directly in a script tag in your html page (decompose will be accessible in global window).
Requirejs :
;
Nodejs :
var decompose = ;// do something
Window global :
Examples
before + base + after
var compose = ; var myFunc = ; var result = ; // return :<b>you say : hello world</b>
around + before + promise + fail
var compose = request = ; // promise based http.request (npm i request-promise) var myFunc = ; ;
API
.after(fn)
var myFunc = ; var result = ; // you should decompose functions
.before(fn)
var myFunc = ; // Yoda style :var result = ; // your functions should decompose
.around(fn)
var myFunc = ; var result = ; // Really, you should decompose functions
.fail(fn)
var myFunc = ; ; // print error : "something is wrong : not enough!" and return the error; // return "12 bitcoins"
Thrown errors are catched :
var myFunc = ; var result = ; // print error
You could recover the error by returning something that is not an Error from fail handlers :
var myFunc = ; var result = ; // return error ("405")var result2 = ; // return "please give more."var result3 = ; // return "25 bitcoins"
.always(fn)
Sugar for .after(fn).fail(fn)
.done(fn)
Alias for .after(fn)
Just there to be compliant with Promise API. It helps to use directly Promise API in composer. (see customisation)
Arguments management
Aside from .around(), every composed functions are wired together. It means that, in compiled order, the return of the previous function is injected as FIRST argument of the next one.
var func = ; var result = ; // hello - world - after
Except in two cases :
return undefined (or return omission)
When you omit to return something (or you return undefined which is the same result) in a composed function, the arguments are kept and reinjected into next function.
var func = ; var myObject = {};; // myObject : { seenInBaseFunction: true, seenInAfterFunction: true }
It works even with multiple arguments :
var func = ; var myObject1 = {};var myObject2 = {}; ; // myObject1 : { foo: true, zoo: true }// myObject2 : { bar: true, lollipop: true }
It has the drawback that there is no way to force undefined return (for the moment) if any composed functions return something.
force multiple arguments
When you want to force multiple arguments, simply return decompose.Arguments([arg1, arg2, ...])
:
var func = ; var result = ; // bananas & apples x 12
As Aspects
Until now, every previous example was wrapping a first function with others (with after, before, around or fail). In that case, the returned composition is fulfilled and it could not be used anymore as relevant function model/aspects. You could always add chained methods (after, before, ...) on it, but as it wraps a function at bottom of its stack, it could not be used as model anymore.
So. First, when you start a (de)composition with no arguments, you also obtain an absolutly standard and callable js function :
var func = // No base function; var result = ; // return "hello World"
It allow you to :
- use it as this (as above)
- fulfill it later (see further)
- use it as model/aspect for other functions/compositions
The idea is to allow inheritance and specialisation management by having functions in objects/prototypes that could be applied together.
var obj = foo: bar: ;// obj.foo and obj.bar are callable and work as a standard functions var obj2 = { // do something else return arg + " world"; }; // both obj and obj2 are workable instance.// But as obj contains (de)compositions, it could be used as model for other objects.{ // (if you want tools that do that nicely for you (and manymore) you should try deep-compiler) forvar i in model objecti = decompose;} ; // of course you could do it with prototypes... ;) obj; // return "hello composition"obj2; // return "hello composition world"obj2; // return "composition rocks!"
decompose.compile(arg1, arg2[, ...])
Merge (wire), from left to right, bunch of functions/compositions together and return the result function without modifying any of provided compositions.
var aFunctionAspect = ; var anotherAspect = ; var { return "Hello " + arg; }; var resultFunction = decompose; // as first argument is a function : resultFunction is fulfilled. var r = ; // return "<b>hello johnny be good.</b>"
Note : when you introduce a pure function (not a composition) higher in "compilation" arguments list (either by providing it as argument of 'decompose' or by merge), it will hide any other function/composition lower in list.
var { return "Hey! " + arg; };var bFunc = ;var { return "Wow! " + arg; }; var func = decompose;// return cFunc. As cFunc is higher in stack : it hides any lower compo/functions. ; // return "Wow! AOP"// only cFunc is fired
var compo = ; var { return "Hey! " + arg; }; var func = decompose; ; // "AOP world rules!"// aFunc has been ignored
decompose.up(arg1, arg2[, ...])
Same thing but modify the first argument if it's a (de)composition. Or if first argument is simple function (i.e. not a (de)composition), return new fulfilled composition.
var aFunctionAspect = ; var anotherAspect = ; // note the differencedecompose; var r = ; // return "<b>johnny be good.</b>"
var { return "Hey! " + arg; };var bFunc = ;var { return "Wow! " + arg; }; var func = decompose;// return cFunc. As cFunc is higher in stack : it hides any lower compo/functions. ; // return "Wow! AOP"// only cFunc is fired
var compo = ; var { return "Hey! " + arg; };var { return "Wow! " + arg; }; // return a clone of compo that is fulfilled with bFunc. aFunc is ignored.var func = decompose; ; // "Wow! AOP rules!"// aFunc has been ignored
Note as result is a 'fulfilled' composition, you could always add chained methods (after, before, ...), but it could not be used anymore as model.
var compo = ;var { return "Wow! " + arg; }; // return a clone of compo that is fulfilled with aFunc.var func = decompose; ; // "Wow! AOP rules!" func; var { return arg + " anything."; }; var func2 = decompose; // "{ Wow! AOP rules! }"// => bFunc has been ignored
decompose.bottom(arg1, arg2[, ...])
Same thing but modify the last argument.
var aFunctionAspect = ; var anotherAspect = ; // note the differencedecomposebottomaFunctionAspect anotherAspect; var r = ; // return "<b>johnny be good.</b>"
var { return "Hey! " + arg; };var bFunc = ;var { return "Wow! " + arg; }; var func = decomposebottomaFunc bFunc cFunc; // return cFunc. As cFunc is higher in stack : it hides any lower compo/functions. ; // return "Wow! AOP"// only cFunc is fired
var compo = ; var { return "Hey! " + arg; };var { return "Wow! " + arg; }; // return compo (not a clone) that is fulfilled with bFunc. aFunc is ignored.decomposebottomaFunc bFunc compo; ; // "Wow! AOP rules!"// aFunc has been ignored
Note as result is a 'fulfilled' composition, you could always add chained methods (after, before, ...), but it could not be used anymore as model.
var compo = ;var { return "Wow! " + arg; }; // return a clone of compo that is fulfilled with aFunc.decomposebottomaFunc compo; ; // "Wow! AOP rules!" compo; var { return arg + " anything."; }; var func2 = decomposebottombFunc compo; // "{ Wow! AOP rules! }"// => bFunc has been ignored
Custom Composer
You could define your own extension of decompose API by creating a Composer
this way :
var myComposer = decompose; var func = ; var result = ; // return "<b>hello John Doe</b>"
Promise compliance
You could return any Promise/Thenable from within composed functions, decompose will wait resolution/rejection before injecting result in next handler (or error in first fail in stack).
Example with request-promise (a simple nodejs http client that return a promise) :
var request = ; // npm i request-promise var func = ; ;
Note : it's equivalent to this :
var request = ; var func = ; ;
or this :
var request = ; var func = ; ;
deep-ocm compliance
It check any deep_ocm flag (deep-ocm boilerplate) on composed functions, and resolve it before each usage. if ocm return null : skip function.
var deep = ; var myOcm = deep; var func = ; deep;; // return 7 deep;; // return 19 deep;; // return 14
Advanced usage
You should know that any composition will forge the effective underlaying function lazzily on first call, and cache the result for further calls. Each time you modify a composition stack, the cached (forged) function is cleared and will be re-forged on next call.
Hand Compilation
If you want to force compilation and obtain the forged function directly (which is not a (de)composition anymore - i.e. no more chaining are allowed), simply invoke myComposition._forge();
var compo = ; var forgedFunction = compo; // allowed :; // not allowed anymore :forgedFunction;
Tests
Under nodejs
You need to have mocha installed globally before launching test.
> npm install -g mocha
Do not forget to install dev-dependencies. i.e. : from 'decompose' folder, type :
> npm install
then, always in 'decompose' folder simply enter :
> mocha
In the browser
Simply serve ./test folder in you favorite web server then open ./test/index.html.
You could use the provided "gulp web server" by entering :
> gulp serve-test
Licence
The MIT License
Copyright (c) 2015 Gilles Coomans gilles.coomans@gmail.com
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.