npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

advisable

v0.2.0

Published

Functional mixin for sync and async before/after/around advice

Downloads

5,635

Readme

advisable.js

advisable.js is an implementation of functional mixins for synchronous and asynchronous before/after/around aspect-oriented advice. It is heavily inspired by a talk on functional mixins by Dan Webb and Angus Croll at FluentConf 2012.

This is certainly not the first JS implementation of advice, nor even the first implementation derived from patterns presented in the FluentConf talk. The goals and motivations behind reinventing this particular wheel are:

  • Adherence to Node.js idioms (sync/async method separation, synchronous versions of methods appended with Sync, callbacks take errors as the first argument by convention).

  • Clear, consistent calling semantics for declaring whether or not advice mutates arguments and return values.

  • Thorough testing. This library injects intermediary methods into call chains and is intended to be used widely to modularize code in applications. As such, extensive testing of all expected use cases is a requirement.

Supported Environments

advisable.js is tested in a Node.js environment and supports CommonJS or AMD before falling back to adding an advisable property on the global object. The library should work in any browser environment with no dependencies provided that either a native or shimmed implementation of Function.prototype.bind is available. Browser support is caveated with should as tests are not currently run in browsers.

Usage

The following usage examples are, quite obviously, contrived to show advice usage with simple arithmetic. In all examples, the object mixing in advisable methods is referred to as the target object, and the method receiving advice is the target method. All examples can be found and executed in examples/advisable.js

The mutate option allows advice callers to declare whether or not advice mutates arguments and return values. mutate defaults to false and the options object may be omitted entirely. Note that this option specifically refers to argument/return value mutation, as advice methods are invoked in the context of the target object, which is mutable within all advice methods.

advisable's around advice is a syntactic shortcut for advising a target with both before and after advise in a single method call. This isi unlike some other implementations, which pass the target function to a wrapper and expect the wrapper to invoke the target.

First, we set up a very simple object with sync and async methods to advise:

function Target(val) {
  this.val = val;
}

Target.prototype.syncFunc = function (a, b) {
  return a + b + this.val;
};

Target.prototype.asyncFunc = function (a, b, callback) {
  process.nextTick(function () {
    callback(null, a + b + this.val);
  }.bind(this));
};

Advice methods are mixed in to a target object by invoking the functional mixin with the target object context.

// Sync/async advice is mixed in separately
advisable.sync.call(Target.prototype);
advisable.async.call(Target.prototype);

Synchronous Usage

First, without advice:

target = new Target(1);

// 10 + 100 + 1 => 111
target.syncFunc(10, 100));

Before advice that changes target object state:

target = new Target(1);
target.beforeSync('syncFunc', function (a, b) {
  this.val++;
});

// 10 + 100 + 2 => 112
target.syncFunc(10, 100);

Before advice that mutates arguments:

target = new Target(1);
target.beforeSync('syncFunc', function (a, b) {
  return [ a/10, b/10 ];
}, { mutate: true });

// 10/10 + 100/10 + 1 => 12
target.syncFunc(10, 100);

After advice that changes target state but does not mutate return value:

target = new Target(1);
target.afterSync('syncFunc', function (a, b) {
  this.val--;
});

// 10 + 100 + 1 => 111 (original return value)
target.syncFunc(10, 100));

// But target.val is now 0 due to decrementing after advice

After advice that mutates a return value:

target = new Target(1);
target.afterSync('syncFunc', function (v) {
  return v * 2;
}, { mutate: true });

// (10/10 + 100/10 + 1) * 2 => 222
target.syncFunc(10, 100);

Around advice that simply observes:

target = new Target(1);
target.aroundSync(
  'syncFunc'
, function (a, b) {
    console.log('around:before called with args: %d, %d', a, b);
  }
, function (a, b) {
    console.log('around:after called with args: %d, %d', a, b);
  }
);

// 10 + 100 + 1 => 111
target.syncFunc(10, 100);

Around advice that mutates arguments and return value:

target = new Target(1);
target.aroundSync(
  'syncFunc'
, function (a, b) {
    return [ a * 3, b * 3 ];
  }
, function (v) {
    return v + 123;
  }
, { mutate: true }
);

// ((10*3) + (100*3) + 1) + 123 => 454
target.syncFunc(10, 100);

Asynchronous Usage

First, without advice:

target = new Target(1);

target.asyncFunc(10, 100, function (err, result) {
  // result = 10 + 100 + 1 => 111
});

Before advice that changes target object state:

target = new Target(1);
target.before('asyncFunc', function (a, b, callback) {
  this.val++;
  // Non-mutated async advice must call back, but the error (first) argument
  // is the only argument considered.
  callback();
});

target.asyncFunc(10, 100, function (err, result) {
  // result = 10 + 100 + 2 => 112
});

Before advice that mutates arguments:

target = new Target(1);
target.before('asyncFunc', function (a, b, callback) {
  callback(null, a/10, b/10);
}, { mutate: true });

target.asyncFunc(10, 100, function (err, result) {
  // result = 10/10 + 100/10 + 1 => 12
});

After advice that changes target object state:

target = new Target(1);
target.after('asyncFunc', function (a, b, callback) {
  this.val--;
  callback();
});

target.asyncFunc(10, 100, function (err, result) {
  // result = 10 + 100 + 1 => 111 (original return value)
  // target.val is now 0 (assuming, of course, that no one else changed it)
});

After advice that mutates the return value:

target = new Target(1);
target.after('asyncFunc', function (v, callback) {
  callback(null, v * 2);
}, { mutate: true });

target.asyncFunc(10, 100, function (err, result) {
  // result = (10/10 + 100/10 + 1) * 2 => 222
});

Around advice that simply observes:

target = new Target(1);
target.around(
  'asyncFunc'
, function (a, b, callback) {
    console.log('around:before called with args: %d, %d', a, b);
    callback();
  }
, function (a, b, callback) {
    console.log('around:after called with args: %d, %d', a, b);
    callback();
  }
);

target.asyncFunc(10, 100, function (err, result) {
  // result = 10 + 100 + 1 => 111
});

Around advice that mutates arguments and return value:

target = new Target(1);
target.around(
  'asyncFunc'
, function (a, b, callback) {
    callback(null, a * 3, b * 3);
  }
, function (v, callback) {
    callback(null, v + 123);
  }
, { mutate: true }
);

target.asyncFunc(10, 100, function (err, result) {
  // result = ((10*3) + (100*3) + 1) + 123 => 454
});

API

For now, see inline documentation in advisable.js

Testing

$ make test

Linting

$ make lint

License

advisable.js is MIT licensed. See LICENSE.