SkillAgentSearch skills...

Introversion

Swiss army knife for debugging JavaScript expressions and performance measurements

Install / Use

/learn @GeneZharov/Introversion
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Introversion.js

Swiss army knife for debugging JavaScript expressions and performance measurements. A wrapper around console.log(), performance.now(), debugger, etc. with essential benefits:

  • works great with functional code (built for React, Redux, Ramda, lodash/fp, etc.)
  • pretty and colorful output
  • easy to use:
    • automatically chooses the best time source available (performance.now() or console.time() or Date.now())
    • logged objects are deeply cloned to prevent confusing results in DevTools
    • lots of tools for various scenarios

Table of Contents

Motivation

Suppose you have an arrow function, and you need to know some value inside:

const fn = n => n + 1; // what's in “n”?

In order to use console.log() you’ll have to rewrite this function into multiple statements:

const fn = n => {
  console.log(n);
  return n + 1;
};

Introversion allows you to simply wrap the desired value without rewriting:

const fn = n => logV(n) + 1; // log value
// ...or
const fn = logF(n => n + 1); // log every function call (arguments and return value)

React Component

A real-world example of a functional React component that makes you hate console.log().

...
renderSuggestion={item => (
  <MenuItem
    text={logV(item).name}
    onClick={_ => this.toggle(item.id)}
  />
)}

Performance

Imagine you need to check if a function call is fast enough to decide whether you need to cache it somewhere. With Introversion you can do it by simply wrapping the desired expression in timeV(() => <expr>) right in JSX:

// before
<Select options={states.map(transform) /* is map() too slow? */} />

// after
<Select options={timeV(() => states.map(transform)) /* prints 2.73ms */} />

Functional Programming

Since Introversion is functional, it tries not to interfere in the program’s logic, but to seamlessly proxy input and output values, so it makes it easy to debug functional code. For example, to research a function composition for issues.

import { pipe, groupBy, omitBy, mapValues } from "lodash/fp";

const build = pipe([
  groupBy(o => o.key),
  logF(omitBy(x => x.length > 1)), // print what goes on the 2nd step and what comes out
  mapValues(([o]) => o.uuid)
]);

Installation

npm install introversion --save-dev
# or
yarn add introversion --dev
import { logV } from "introversion";

logV(val);

Advanced installation cases are described below (Default import, Set up in global variable, Zero-conf for Node.js)

Watchers

logV()

Alias: v() (helpful with default import: In.v())

logV() (“v” stands for “value”) merely prints an array of its arguments. The main difference from console.log() is that the last argument is returned. Therefore it is safe to wrap any expression in logV() without breaking your code down.

import { logV } from "introversion";

const random = n => Math.floor(logV(Math.random()) * n) + 10;
random(1); //=> logV() [ 0.5909956243063763 ]

You can print any other values alongside with the wrapped expression. Just pass them as arguments. Only the last argument is returned so extra arguments won’t affect your code:

const random = n => Math.floor(logV(num, this, "mystr", Math.random()) * n) 10;
random(1); //=> logV [ 1, {}, 'mystr', 0.8474771121023132 ]

You can use extra arguments to distinguish different watchers from each other in the log:

const fn = n => n > 0 ? logV(true, n * 1.25) : logV(false, n / 9);
fn(5);   //=> logV [ true, 6.25 ]
fn(-81); //=> logV [ false, -9 ]

logF()

Alias: f() (helpful with default import: In.f())

logF() (“f” stands for “function”) is designed to watch for function calls. When a wrapped function is called, its arguments and a returned value are logged. If a wrapped function throws an exception, that exception will be logged and then rethrown again. A wrapped in the logF() function can be used in the same way as an unwrapped one: all arguments, this and a returned value will be proxied.

import { logF } from "introversion";

[1, 2].map(logF(n => 2 * n));

//=> logF()
//=> ... Params: [ 1, 0, [ 1, 2 ] ]
//=> ... Result: 2

//=> logF()
//=> ... Params: [ 2, 1, [ 1, 2 ] ]
//=> ... Result: 4

logF() can also accept additional arguments that will be printed just like logV() does:

logF("foo", "bar", calculate)(1, 2, 3)

// => logF() [ "foo", "bar" ] <- extra arguments go here
// => ... Params: [ 1, 2, 3 ]
// => ... Result: 999

Timers

time(), timeEnd()

A replacement for console.time/timeEnd() that use the most accurate source of time available:

  1. performance.now()
  2. console.time/timeEnd()
  3. Date.now()
import { time, timeEnd } from "introversion";

time(); // start the timer
calculateSomething();
timeEnd(); // stop the timer

//=> timeEnd() 203 ms

Just like console timing methods, these functions accept an optional name for a new timer. timeEnd() may also take additional arguments for printing alongside with the elapsed time (not available with format: false and console methods as a time source).

time("label"); // start the timer named "label"
calculateEverything();
timeEnd("foo", "bar", "label"); // stop the timer named "label"

//=> timeEnd() [ 'foo', 'bar', 'label' ] 203 ms

stopwatch(), lap()

When you have a sequence of actions, it is inconvenient to wrap every action in time()...timeEnd(). In this case, stopwatch API is more helpful.

  • stopwatch() — initially starts the timer.
  • lap([...args]) — prints the elapsed time since the previous stopwatch/lap(). Also prints optional arguments and starts a new timer for the next lap. </a>
import { stopwatch, lap } form "introversion";

stopwatch();

createTables();
lap("created"); //=> lap() [ 'created' ] 15 ms

const rows = queryRows();
lap("foobar", rows, "queried"); //=> lap() [ 'foobar', [], 'queried' ] 107 ms

populateState(rows);
lap("populated"); //=> lap() [ 'populated' ] 768 ms

timeF()

You can wrap any function in timeF(). The result will be a function with the same behavior as a wrapped one, but additionally, it will print the execution time of its synchronous code.

import { timeF } form "introversion";

array.map(timeF(iterator));

//=> timeF() 4 ms
//=> timeF() 9 ms
//=> timeF() 1 ms

Optionally you can pass any arguments for printing:

array.map(timeF("foo", "bar", iterator));

//=> timeF() [ 'foo', 'bar' ] 4 ms
//=> timeF() [ 'foo', 'bar' ] 9 ms
//=> timeF() [ 'foo', 'bar' ] 1 ms

timeV()

Sometimes you may suspect that some expression is calculated for too long. In this case it is convenient to wrap that expression in timeV(() => <expr>) that will print the elapsed time.

import { timeV } form "introversion";

// original expression
data = [calculate(src), readState()];

// wrapped expression
data = timeV(() => [calculate(src), readState()]);

//=> timeV() 349 ms

Optionally you can pass any arguments for printing:

data = timeV("data", src, () => [calculate(src), readState()]);

//=> timeV() [ 'data', 'DATABASE' ] 349 ms

Modes

Quiet Mode

  • logV_(), alias: v_()
  • logF_(), alias: f_()

Sometimes you are not interested in a wrapped value itself, but you need to know, if it was calculated. For example, in React Native an attempt to log an event object may hang the application. Or maybe you are interested only in printing additional arguments. For these cases, there are alternative quiet mode watchers that don’t log wrapped value itself but log all additional arguments.

import { logF_ } from "introversion";

const fn = logF_("Invoked!", n => n + 1);
fn(2); //=> logF_() [ 'Invoked!' ]

Breakpoint Mode

  • debV()
  • debF()

Instead of printing data, these functions create a breakpoint using debugger statement. It can help to look around and walk through the call stack.

Guards

Sometimes a watcher can produce too many outputs if it is called for too many times. You may want to suppress excess outputs. Perhaps you need only the first one or first ten outputs. In this case, the in-place “guard” option may help. It specifies the number of times a watcher will be active. After this amount runs out, it will merely proxy values without any side effects. More about the in-place configuration is described below.

import { logV } from "introversion";

for (le
View on GitHub
GitHub Stars8
CategoryDevelopment
Updated5y ago
Forks0

Languages

JavaScript

Security Score

70/100

Audited on Jun 4, 2020

No findings