1.0.2 • Published 3 years ago

decorator-wrapper-js v1.0.2

Weekly downloads
-
License
MIT
Repository
github
Last release
3 years ago

DecoratorWrapperJS

decorators are simple!

Home

npm.io

This library introduces decorators and provides an easy way to use them!

What are decorators?

In other languages like Python decorator is a function that wraps around an original function and adds additional logic before, after, or borth before and after an original function without making any changes to the original function's code.

Why do we need to use decorators?

Decorators are usually used when we either cannot or don't want to change the original code of the funciton but rather add some additional funcitonality to it. Even if you think that changing the original code of the funciton once when you need something to it is a good idea, then think about if you need to add this exact change to 10 more different functions. That's when decorators come to our rescue.

Decorators might be used for:

  • benchmarking
  • counting function calls
  • caching function results
  • checking argument type at runtime
  • metaprogramming

and many more! source

Example of a decorator

An example of a decorator function is shown below:

const decorator = (func) => {
    return ((...args) => {
        console.log('Code executed before');

        let res = func(...args);

        console.log('Code executed after');
        return res;
    });
};

const actualFunction = (a, b) => {
    console.log('Decorators are not confusing... Right?... Right?... Ah?!!!');
    return a + b;
};

const decoratedFunction = decorator(actualFunction);

const sum = decoratedFunction(1, 1);
console.log('Result is:', sum);

The result of the above code will be:

Code executed before
Decorators are not confusing... Right?... Right?... Ah?!!!
Code executed after
Result is: 2

To be honest, the abovementioned example is not the most concise one and clear one. A lot of people would say that decorators are confusing. Well, I would also agree with many about the first part of the statement. Decorators really may become confusing.

However, with this library it is now possible to use decorators in your JavaScript code easily.

How it works

Step 1

Import the package

const decoratorjs = require('decorator-wrapper-js');
const dec = decoratorjs.dec;

Step 2

Create a decorator object (decorator) using spawn or spawnAsync function (async funtions will be discussed later below):

dec.newDecorator = dec.spawn((...args) => console.log('before:', ...args)
                     , (res) => { console.log('after:', res); return res; }
                 );

Note: you don't have to assign the decorator object to dec.newDecorator (where newDecorator is a name of your decorator) - it is just a possible way of keeping decorators in one place

Note: the spawn function has its synonym - create function which works exactly the same

Note: the first argument to the spawn is a funtion that will be executed before the decorated funtion. The second argument is a function that will be executed after the decorated funtion. If you don't want have a funtion either before or after then you MUST pass null instead of that funtion to the spawn function

// only code that executes after
dec.newDecorator = dec.spawn(null
                     , (res) => { console.log('after:', res); return res; }
                 );
// only code that executes before
dec.newDecorator = dec.spawn((...args) => console.log('before:', ...args)
                     , null
                 );

If you think that it is confusing and easy to forget to add null parameter if you only need a function that executes before, then use a spawnRest or createRest (or spawnRestAsync for async funtions).

For example:

// only code that executes before with spawnRest method
dec.newDecorator = dec.spawnRest((...args) => console.log('before:', ...args));

spawnRest means spawn restricted (spawn in restricted mode) and that is because standard spawn function also has a functinality of passing it a single function instead of before and after paramters. It is a convenience syntax which is called single function functionallity (in terms of this library) and looks like the following:

dec.newDecorator = dec.spawn((func, ...args) => {
    console.log('before'); // insert code executed before the funciton here
    let res = func(...args);
    console.log('after'); // insert code executed after the funciton here
    return res;
});

Note the list of arguments to the funtion (the first argument is always func)

Step 3

Decorate function with your created decorator and return it

const actualFunction = (a, b) => {
    return a + b;
};

let decedFunction = dec.newDecorator.use(actualFunction).yield();

Note: method use will return decorator object back (this is used for chaining decorators). You will not receive a callable function that has been decorated until you call yield method (or get which is a synonym to yield)

Now you can use your decorated function like the following:

const res = decedFunction(1, 1); // the res will equal to 2

Chaining decorators

Decorators can be chained together. When chaining decorators they will be executed from the outer one to the inner one.

Chaining is done by using method chain of the decorator object itself.

The following example shows how to chain several decorators together:

dec.firstDecorator = dec.spawn((func, ...args) => {
    console.log('before 1'); // insert code executed before the funciton here
    let res = func(...args);
    console.log('after 1'); // insert code executed after the funciton here
    return res;
});

dec.secondDecorator = dec.spawn((func, ...args) => {
    console.log('before 2'); // insert code executed before the funciton here
    let res = func(...args);
    console.log('after 2'); // insert code executed after the funciton here
    return res;
});

dec.thirdDecorator = dec.spawn((func, ...args) => {
    console.log('before 3'); // insert code executed before the funciton here
    let res = func(...args);
    console.log('after 3'); // insert code executed after the funciton here
    return res;
});

const actualFunction = (a, b) => {
    console.log('actualFunciton');
    return a + b;
};

let decedFunction = dec.firstDecorator.use(actualFunction)
                                    .chain(dec.secondDecorator)
                                    .chain(dec.thirdDecorator)
                                    .yield();

console.log('Result is:', decedFunction(1, 1));

The result of the code above will be:

before 3
before 2
before 1
actualFunciton
after 1
after 2
after 3
Result is: 2

Other methods

embed - creates a decorator object inside the package's export object (embedAsync for async)

embedRest - creates a decorator object inside the package's export object in restricted mode (embedAsyncRest for async in restricted mode)

rem (synonyms are rm, remove) - removes a decorator object that was previously added to he package's export object

Example:

const decoratorjs = require('decorator-wrapper-js');
const dec = decoratorjs.dec;

dec.embed('firstDecorator', (...args) => console.log('before 1') // insert code executed before the funciton here
                     , (res) => { console.log('after 1'); return res; } // insert code executed after the funciton here
                 );


dec.embed('secondDecorator', (func, ...args) => {
    console.log('before 2'); // insert code executed before the funciton here
    let res = func(...args);
    console.log('after 2'); // insert code executed after the funciton here
    return res;
});

let firstDecorator = dec.firstDecorator // get firstDecorator
let secondDecorator = dec.secondDecorator // get secondDecorator

dec.rem('firstDecorator');
dec.rem('secondDecorator');

firstDecorator = dec.firstDecorator // undefined
secondDecorator = dec.secondDecorator // undefined

An example with syncronous functions

const decoratorjs = require('decorator-wrapper-js');
const dec = decoratorjs.dec;

dec.firstDecorator = dec.spawn((...args) => console.log('before 1') // insert code executed before the funciton here
                     , (res) => { console.log('after 1'); return res; } // insert code executed after the funciton here
                 );

dec.secondDecorator = dec.spawn((func, ...args) => {
    console.log('before 2'); // insert code executed before the funciton here
    let res = func(...args);
    console.log('after 2'); // insert code executed after the funciton here
    return res;
});

dec.thirdDecorator = dec.spawn((func, ...args) => {
    console.log('before 3'); // insert code executed before the funciton here
    let res = func(...args);
    console.log('after 3'); // insert code executed after the funciton here
    return res;
});

const actualFunction = (a, b) => {
    console.log('actualFunciton');
    return a + b;
};

let decedFunction = dec.firstDecorator.use(actualFunction)
                                    .chain(dec.secondDecorator)
                                    .chain(dec.thirdDecorator)
                                    .yield();

console.log('Result is:', decedFunction(1, 1));

The output will be:

before 3
before 2
before 1
actualFunciton
after 1
after 2
after 3
Result is: 2

Decorators with async

In order to use the code with async functions the library exposes the following functions:

  • spawnAsync (synonym is createAsync) - an async analogue of the spawn function
  • spawnAsyncRest (synonym is createAsyncRest) - an aync analogue of the spawnRest function
  • embedAsync - an analogue of the embed function
  • embedAsyncRest - an analogue of the embedRest function

An example with async

const decoratorjs = require('decorator-wrapper-js');
const dec = decoratorjs.dec;

function later(delay) {
    return new Promise(function(resolve) {
        setTimeout(resolve, delay);
    });
}


async function actualFunction(a, b) {
    console.log('actualFunciton');
    return await later(1000).then(() => a + b);
}


dec.firstDecorator = dec.spawnAsync(async (func, a, b) => {
    await later(5000).then(() => console.log('before 1'));
    let res = func(a, b);
    await later(5000).then(() => console.log('after 1'));
    return res;
});


// different methods of decorator creation are shown below:
dec.secondDecorator = dec.spawnAsync(async (a, b) => await later(5000).then(() => console.log('before 2 (args: ' + a + ', ' + b + ')'))
                    , async (res) => { await later(5000).then(() => console.log('after 2 (result is: ' + res + ')')); return res; }
                );

thirdDecorator = async (func, a, b) => {
    await later(5000).then(() => console.log('before 3'));
    let res = await func(a, b);
    await later(5000).then(() => console.log('after 3'));
    return res;
};
dec.thirdDecorator = dec.spawnAsync(thirdDecorator);


let decoratedFunc = dec.firstDecorator.use(actualFunction)
                                    .chain(dec.secondDecorator)
                                    .chain(dec.thirdDecorator)
                                    .yield();

const main = async () => {
    const res = await decoratedFunc(1, 1);
    console.log('Result:', res);
}

main();

The output will be:

before 3
before 2 (args: 1, 1)
before 1
actualFunciton
after 1
after 2 (result is: 2)
after 3
Result: 2

Short reference

Functions exposed by the library:

NameParametersDescription
spawnbefore (funciton), after (function) or a single decorator funtioncretes a decorator object
createbefore (function), after (function) or a single decorator funtionthe same as spawn
embeddecoratorName (string), before (funciton), after (function) or decoratorName (string), a single decorator funtioncreates a decorator object inside the package's export object
spawnRestbefore (funciton), after (function) or a single decorator funtioncreates a decorator object in restricted mode (e. g. without a "single function functionallity")
createRestbefore (funciton), after (function) or a single decorator funtionthe same as spawnRest
embedRestdecoratorName (string), before (funciton), after (function) or decoratorName (string), a single decorator funtioncreates a decorator object inside the package's export object in restricted mode
spawnAsyncbefore (funciton), after (function) or a single decorator funtionanalogue of spawn for async funtions
createAsyncbefore (funciton), after (function) or a single decorator funtionthe same as spawnAsync
embedAsyncdecoratorName (string), before (funciton), after (function) or decoratorName (string), a single decorator funtionanalogue of embed for async funtions
spawnAsyncRestbefore (funciton), after (function) or a single decorator funtionthe same as spawnAsync but in restricted mode
createAsyncRestbefore (funciton), after (function) or a single decorator funtionthe same as spawnAsyncRest
embedAsyncRestdecoratorName (string), before (funciton), after (function) or decoratorName (string), a single decorator funtionanalogue of embedRest for async funtions
remdecoratorName (string)removes a decorator object that was previously added to he package's export object
rmdecoratorName (string)the same as rem
removedecoratorName (string)the same as rem

Methods of the decorator object:

NameParametersDescription
yield-returns the decorated function from the decorator
get-the same as yield
usefunctionbinds a function to the decorator (e. g. executes the funtion within the decorator)
wrapfuncitonthe same as use
wrapAroundfunctionthe same as use
chaindecorator objectchains current decorator with another decorator (e. g. executes another decorator's code then current one's)

Notes

Do NOT use use function in the middle of chaining or anywhere except before the chaining:

// BAD EXAMPLE, DO NOT DO THIS!
let decoratedFunc = dec.firstDecorator.use(actualFunction)
                                    .chain(secondDecorator)
                                    .chain(dec.thirdDecorator)
                                    .use(someFunc) // this line is incorrect and should be removed
                                    .chain(dec.thirdDecorator)
                                    .yield();

An example above leads to an undefined behaviour as use and chain are not supposed to work like that

1.0.2

3 years ago

1.0.2-0

3 years ago

1.0.1

3 years ago

1.0.0

3 years ago