Skip to content

Nuanced implementation of `once` functionality, optimized for node.js

License

Notifications You must be signed in to change notification settings

ahwayakchih/nuonce

Repository files navigation

nuonce

Yet another implementation of once - a function wrapper that will call a target function just once. Every other call after the first one will return result from the first call.

It provides just a few methods. Each one results in a function better resembling a target than the previous one, for an additional cost in speed.

nuonce.stripped is for use when target functions do not have any custom properties, or when those properties are not needed to be accessible on the returned function. It is A LOT faster than any of the other methods and seems to be a good replacement for popular once module in most use cases.

nuonce.observable supports additional cb argument that, if provided, must be a function. It will be called every time "nuonced" function is called. It can be used to implement additional stuff, like called and value properties created by once module, or to throw an error, like with once.strict or onetime module. All the while running faster than those other modules.

nuonce.copied gives comparable results to the one returned by once module, only slightly faster and with the same length as target function, but without custom called and value properties (unless cb that implements them is provided, but then it costs additional speed).

nuonce.proxied returns callable object (ES6 Proxy) that provides access to all of custom properties and methods of a target function, including those added in future (after "nuonced" version was returned).

Nuonce is meant to be used in Node.js applications. Although it may work in web browsers, it is tested and optimized for Node.js (with V8 engine).

Installation

npm install nuonce

or:

npm install https://github.com/ahwayakchih/nuonce

Usage

Some example uses:

const nuonce = require('nuonce');

const target = function (a, b, c) {
  console.log('called with:', a, b, c);
  return Math.random();
}
target.foo = 42;

// When custom properties and keeping function `length` are not needed:
var f = nuonce.stripped(target);
f.length;    // 0
f.foo;       // undefined
f(1, 2, 3);  // called with: 1, 2, 3
f() === f(); // true

// When custom properties and keeping function `length` are not needed,
// but additional `called` is:
var f = nuonce.observable(target, status => {
  f.called = status.calls;
  return status.value;
});
f.length;      // 0
f.foo;         // undefined
f(1, 2, 3);    // called with: 1, 2, 3
f.called === 1;// true
f() === f();   // true
f.called === 3;// true

// When properties can be just copied:
var f = nuonce.copied(target);
target.foo = 142;
f.length;    // 3
f.foo;       // 42
f(1, 2, 3);  // called with: 1, 2, 3
f() === f(); // true

// When properties should always be kept up-to-date:
var f = nuonce.proxied(target);
target.foo = 242;
f.length;    // 3
f.foo;       // 242
f(1, 2, 3);  // called with: 1, 2, 3
f() === f(); // true

Compatibility

Nuonce is compatible with once module for most use cases found in Node.js projects, i.e., when none of called or value properties set on "onced" function are used.

Usually, once is called on a function that does not have any custom properties (or they are not accessed through "onced" version of that function anyway), e.g., "cb" or "callback" functions passed to asynchronous tasks. In those cases nuonce.stripped will give a lot of speedup without sacrificing anything.

In cases when called or value propety is needed, you can use custom callback to set them up. For example:

function target () {
  return 'I am here!';
}

// Using "once" module
const once = require('once');

var f = once.strict(target);

f();
if (f.called) {
  // do something with f.value
}

// Using "nuonce" module
const nuonce = require('nuonce/copied');

var f = nuonce(target, function onTargetCalled (status) {
  if (status.calls > 1) throw new Error('Do not call this multiple times');
  f.called = true;
  return f.value = status.value;
});
f.called = false;
f.value = undefined;

f();
if (f.called) {
  // do something with f.value
}

With additional wrapper to set the called property, nuonce.copied runs slower than once and code is less convenient to write. If you really need to use the called property often, it probably will be better to stick with the once module. Unless you want called property, but do not need properties of original function. In which case nuonce.observable with additional callback is faster than once but less convenient to write.

For browser use, it may be easier to just use the once.js module, although only in cases where nuonce.stripped could be used (both are implemented almost exactly the same way, but once.js seems to outerform everything).

Benchmarks

Some benchmarks results for comparison (you can re-run them locally with: npm run benchmarks, after running npm install --no-shrinkwrap):

Note: while nuonce can probably still run in Node.js as old as 6.9, benchmarks and tests require at least v14.

Running inside container (Alpine Linux v3.18) with Node v18.16.1 and Intel(R) Core(TM) i7-3537U CPU @ 2.00GHz x 2

Testing:
- once    v1.4.0 https://github.com/isaacs/once#readme           
- once.js v0.0.4 https://github.com/daniellmb/once.js            
- onetime v6.0.0 https://github.com/sindresorhus/onetime#readme  
- nuonce  v2.0.1 https://github.com/ahwayakchih/nuonce           

Test function with 0 properties, called 1 time with 1 argument

  11 tests completed.

  nuonce.stripped              x 8,377,280 ops/sec ±0.21% (92 runs sampled)
  once.js                      x 8,367,015 ops/sec ±0.26% (88 runs sampled)
  nuonce.observable            x 7,678,453 ops/sec ±0.21% (93 runs sampled)
  nuonce.copied                x 6,081,398 ops/sec ±0.34% (95 runs sampled)
  nuonce.observable + callback x 5,872,795 ops/sec ±0.18% (92 runs sampled)
  once                         x 4,783,971 ops/sec ±0.49% (88 runs sampled)
  nuonce.copied + callback     x 4,775,697 ops/sec ±0.29% (93 runs sampled)
  once.strict                  x 3,980,441 ops/sec ±0.17% (93 runs sampled)
  nuonce.proxied               x 3,549,031 ops/sec ±0.13% (94 runs sampled)
  nuonce.proxied + callback    x   369,444 ops/sec ±0.22% (93 runs sampled)
  onetime                      x    79,023 ops/sec ±0.74% (89 runs sampled)


Test function with 0 properties, called 50 times with 1 argument

  10 tests completed.

  nuonce.stripped              x 508,103 ops/sec ±0.21% (94 runs sampled)
  nuonce.copied                x 505,040 ops/sec ±0.28% (89 runs sampled)
  once.js                      x 504,693 ops/sec ±0.21% (93 runs sampled)
  nuonce.observable            x 477,380 ops/sec ±0.23% (96 runs sampled)
  nuonce.copied + callback     x 465,862 ops/sec ±0.13% (95 runs sampled)
  nuonce.observable + callback x 463,619 ops/sec ±0.26% (91 runs sampled)
  once                         x 439,328 ops/sec ±0.42% (88 runs sampled)
  nuonce.proxied               x 146,797 ops/sec ±0.14% (93 runs sampled)
  nuonce.proxied + callback    x  98,983 ops/sec ±0.17% (91 runs sampled)
  onetime                      x  61,114 ops/sec ±0.55% (93 runs sampled)


Test function with 3 properties, called 1 time with 1 argument

  11 tests completed.

  nuonce.stripped              x 8,652,826 ops/sec ±0.34% (92 runs sampled)
  once.js                      x 8,524,248 ops/sec ±0.44% (92 runs sampled)
  nuonce.observable            x 7,927,410 ops/sec ±0.26% (92 runs sampled)
  nuonce.observable + callback x 5,975,545 ops/sec ±0.21% (92 runs sampled)
  nuonce.proxied               x 3,454,379 ops/sec ±0.14% (93 runs sampled)
  nuonce.copied                x 2,565,246 ops/sec ±0.17% (94 runs sampled)
  once                         x 2,262,805 ops/sec ±0.37% (90 runs sampled)
  nuonce.copied + callback     x 2,184,450 ops/sec ±0.76% (94 runs sampled)
  once.strict                  x 2,089,327 ops/sec ±0.16% (91 runs sampled)
  nuonce.proxied + callback    x   370,874 ops/sec ±0.23% (92 runs sampled)
  onetime                      x    71,770 ops/sec ±0.69% (91 runs sampled)


Test function with 3 properties, called 50 times with 1 argument

  10 tests completed.

  nuonce.stripped              x 515,852 ops/sec ±0.19% (95 runs sampled)
  once.js                      x 504,774 ops/sec ±0.26% (94 runs sampled)
  nuonce.observable            x 488,056 ops/sec ±0.23% (94 runs sampled)
  nuonce.observable + callback x 463,799 ops/sec ±0.25% (90 runs sampled)
  nuonce.copied                x 442,438 ops/sec ±0.13% (93 runs sampled)
  nuonce.copied + callback     x 416,070 ops/sec ±0.51% (89 runs sampled)
  once                         x 400,605 ops/sec ±0.64% (91 runs sampled)
  nuonce.proxied               x 141,235 ops/sec ±0.14% (92 runs sampled)
  nuonce.proxied + callback    x  98,343 ops/sec ±0.15% (93 runs sampled)
  onetime                      x  58,426 ops/sec ±0.49% (93 runs sampled)

You can also start a single benchmark run by calling:

npm run benchmark

For a single benchmark run, you can optionally override default number of arguments, properties and calls. For example:

ARGS=2 PROPS=0 CALLS=30 npm run benchmark

About

Nuanced implementation of `once` functionality, optimized for node.js

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published