You have a chain. Something in the middle is wrong. You want to see the value between two calls without rewriting the chain into five consts.
Here's the chain we'll poke at:
const orders = [
{ id: 1, total: 120, paid: true },
{ id: 2, total: 80, paid: false },
{ id: 3, total: 45.5, paid: true },
];
const revenue = orders
.filter((o) => o.paid)
.map((o) => o.total)
.reduce((sum, n) => sum + n, 0);
revenue is 165.5. But say it wasn't, and you want to know what .map handed to .reduce.
1. The console.log(x) || x trick
console.log returns undefined, so console.log(x) || x logs x and then evaluates to x. Drop it into any arrow function and the chain keeps working:
const revenue = orders
.filter((o) => o.paid)
.map((o) => console.log(o.total) || o.total)
.reduce((sum, n) => sum + n, 0);
// logs 120, then 45.5
It works everywhere. It also looks like a bug to the next person, so take it out before you commit.
2. A tap helper
If you do this often, give it a name. tap logs a value and passes it through unchanged:
const tap = (label) => (value) => {
console.log(label, value);
return value;
};
const revenue = orders
.filter((o) => o.paid)
.map(tap("paid order"))
.map((o) => o.total)
.reduce((sum, n) => sum + n, 0);
tap shines in promise chains, where each .then gets one value:
fetch("https://api.github.com/zen")
.then((res) => res.text())
.then(tap("zen"));
3. Split it and read the pieces
The honest fix is often to name the steps:
const paid = orders.filter((o) => o.paid);
const totals = paid.map((o) => o.total);
const revenue = totals.reduce((sum, n) => sum + n, 0);
Now each step is a line, and you can log any of them. The cost is three console.logs you'll add, read, and delete.
The same thing in a scratchpad
This is the loop that JSLab, a JavaScript & TypeScript scratchpad for macOS, is built around. Two features cover all three cases above.
Auto Log. Every top-level expression is logged beside its line, so a bare name is enough:
const paid = orders.filter((o) => o.paid);
paid; // logged: the two paid orders
paid.length; // logged: 2
Assignments, x++ and console.* calls are never auto-logged, so the output stays readable.
Magic comments. End a line with //? and JSLab logs exactly that statement, even inside a function or a loop. /*?*/ works mid-expression, so you can look inside the chain without breaking it:
orders.filter((o) => o.paid) /*?*/ .map((o) => o.total);
That logs the filtered array, before .map runs. And //? $.length logs an expression built from the value, which is $:
const totals = orders.filter((o) => o.paid).map((o) => o.total); //? $.length
That logs 2 instead of the whole array.
Here are all three in one JSLab tab: //? inside a function (line 4), /*?*/ on a loop (line 7), and //? $.length (line 12), which logs 2.
Every row in the output is tagged with the line that produced it, and clicking the line number jumps the editor there. Nothing to remove before you commit: there's no console.log to forget.
Which one when
- Shipping code, or code someone else reads: split it into named steps.
- Quick look in a real project:
tap, or|| xif you promise to delete it. - Working something out in a scratchpad: let Auto Log and
//?do it.
JSLab launches Nov 15. The docs for Auto Log and magic comments are here: https://js-lab.app/docs/editor

Top comments (0)