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 🙏

© 2025 – Pkg Stats / Ryan Hefner

better-console-group

v0.2.20

Published

Better API for console.group.

Readme

better-console-group Node Deno codecov

Use console.group without worrying about mismatched console.groupEnd. Group multiple entries of console.log/console.warn/console.error in an async function without interference from other entries created by others during await.

betterGroup

The betterGroup function is a better version of the console.group and console.groupEnd pair. It wraps around your function so every console method call inside is part of the group.

import { betterGroup } from 'better-console-group';
console.log('before the group');
const result = betterGroup('group name', () => {
  console.log('inside the group')
  return 42;
});
console.log('after the group');
console.log(result); // 42

API

function betterGroup<T>(label: string, callbackFn: () => T, thisArg?: unknown): T;

The betterGroup function takes 2 required arguments and 1 optional argument.

  • label: This argument is the string label. It's the same one as you would pass into console.group(label).
  • callbackFn: This argument is a function. All the console method calls inside will be called between console.group(label) and console.groupEnd().
  • thisArg: This optional argument will be used as the this context inside the callbackFn.

The return value from the callbackFn will be the return value from the betterGroup call.

asyncGroup

The asyncGroup function is the async version of the betterGroup function. When multiple asyncGroup are running in parallel, the groups they create won't overlap with each other. Each group will only flush its messages when its async function has finished running.

import { asyncGroup } from 'better-console-group';
const downloadPromises = [];
for (let imageURL in imageURLs) {
  const downloadPromise = asyncGroup(`${imageURL}`, async (group) => {
    group.log('Start downloading image...');
    try {
      await fetch(imageURL);
      group.log('Finish downloading image.');
    } catch {
      group.error('Fail to download image!')
    }
  });
  downloadPromises.push(downloadPromise);
}
await Promise.allSettled(downloadPromises);

In the example from above, image downloading can happen in parallel. However, each group that represents a single download will not overlap with each other. If one download fails, we can clearly see which image URL it's associated with.

API

asyncGroup

async function asyncGroup<T>(label: string, callbackFn: (group: AsyncConsoleGroup) => T, thisArg?: unknown): Promise<T>;

asyncGroup is almost identical to betterGroup, exact it passes a group argument when calling callbackFn. We need to call console methods on the group instead of straight from the console.

AsyncConsoleGroup

An instance of AsyncConsoleGroup is passed into the callbackFn when asyncGroup is called.

group.log

group.log(message: any, ...optionalParams: Array<any>): void

Similar to console.log, but for logging a message inside an asyncGroup callback.

group.warn

group.warn(message: any, ...optionalParams: Array<any>): void

Similar to console.warn, but for logging a warning inside an asyncGroup callback.

group.error

group.error(message: any, ...optionalParams: Array<any>): void

Similar to console.error, but for logging an error inside an asyncGroup callback.

group.asyncGroup

async group.asyncGroup<T>(
    label: string,
    callbackFn: (group: AsyncConsoleGroup) => Promise<T>,
    thisArg?: unknown,
  ): Promise<T>

The group.asyncGroup signature is identical to the top level asyncGroup function. It creates a nested group inside an existing group. Below is an example of how this may be used.

import { asyncGroup } from 'better-console-group';
const processingPromises = [];
for (let imageURL in imageURLs) {
  const processingPromise = asyncGroup(`${imageURL}`, async (group) => {
    group.log('Start processing image...');

    const image = await group.asyncGroup('Download image', async (nestedGroup) => {
      nestGroup.log('Start downloading image...');
      try {
        return await downloadImage(imageURL);
        nestedGroup.log('Finish downloading image.');
      } catch {
        nestedGroup.error('Fail to download image!')
      }
    });

    if (image) {
      await group.asyncGroup('Resize image', async (nestedGroup) => {
        nestGroup.log('Start resizing image...');
        try {
          await resizeImage(image);
          nestedGroup.log('Finish resizing image.');
        } catch {
          nestedGroup.error('Fail to resize image!')
        }
      });
    }

    group.log('Finish processing image.');=
  });
  processingPromises.push(processingPromise);
}
await Promise.allSettled(processingPromises);

In this example, each image URL has its own group. Within each group, there's one nested group for logs related to the downloading of the image. There's another nested group for logs related to the resizing of the image.