Last active
August 27, 2025 01:57
-
-
Save pi0/42f1c168d24d5bdef336f7a0f5bf2026 to your computer and use it in GitHub Desktop.
Thenable vs Promise instance check
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| clk: ~4.24 GHz | |
| cpu: Apple M4 Pro | |
| runtime: node 22.18.0 (arm64-darwin) | |
| benchmark avg (min … max) p75 / p99 (min … top 1%) | |
| ------------------------------------------------- ------------------------------- | |
| • single check per iteration | |
| ------------------------------------------------- ------------------------------- | |
| instanceof Promise 1.55 ns/iter 1.59 ns 2.07 ns ▃█▂▇▄▁▂▁▁▁▁ | |
| typeof x.then === "function" 3.72 ns/iter 3.92 ns 4.87 ns ▁▂▄█▃▆▃▁▁▁▁ | |
| summary | |
| instanceof Promise | |
| 2.4x faster than typeof x.then === "function" | |
| • batch of 16 checks per iteration | |
| ------------------------------------------------- ------------------------------- | |
| instanceof Promise (x16) 26.84 ns/iter 28.02 ns 30.80 ns ▂▃█▅▅▆█▃▂▂▂ | |
| typeof x.then === "function" (x16) 63.57 ns/iter 65.14 ns 68.84 ns ▁▃▄▇███▆▄▂▁ | |
| summary | |
| instanceof Promise (x16) | |
| 2.37x faster than typeof x.then === "function" (x16) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // bench.mjs | |
| import { bench, summary, compact, group, run } from "mitata"; | |
| // Two checks | |
| const isPromiseInstance = (x) => x instanceof Promise; | |
| const isThenable = (x) => typeof x?.then === "function"; | |
| // Mixed inputs: native Promises, thenables, and non-promises | |
| const items = [ | |
| Promise.resolve(1), | |
| Promise.reject(new Error("x")).catch(() => 0), // settled promise | |
| { then: (f) => f?.() }, // plain thenable | |
| 42, | |
| null, | |
| { a: 1 }, | |
| async () => 1, // function, not thenable itself | |
| "str", | |
| ]; | |
| // Simple rotator to avoid constant-folding | |
| let i = 0; | |
| const next = () => { | |
| const v = items[i]; | |
| i = (i + 1) % items.length; | |
| return v; | |
| }; | |
| // Sink to prevent dead-code elimination | |
| let sink = 0; | |
| group("single check per iteration", () => { | |
| summary(() => { | |
| compact(() => { | |
| bench("instanceof Promise", () => { | |
| const v = next(); | |
| sink ^= isPromiseInstance(v) | 0; | |
| }); | |
| bench('typeof x.then === "function"', () => { | |
| const v = next(); | |
| sink ^= isThenable(v) | 0; | |
| }); | |
| }); | |
| }); | |
| }); | |
| group("batch of 16 checks per iteration", () => { | |
| summary(() => { | |
| compact(() => { | |
| bench("instanceof Promise (x16)", () => { | |
| let acc = 0; | |
| for (let k = 0; k < 16; k++) acc ^= isPromiseInstance(next()) | 0; | |
| sink ^= acc; | |
| }); | |
| bench('typeof x.then === "function" (x16)', () => { | |
| let acc = 0; | |
| for (let k = 0; k < 16; k++) acc ^= isThenable(next()) | 0; | |
| sink ^= acc; | |
| }); | |
| }); | |
| }); | |
| }); | |
| await run(); | |
| // tiny use of sink so Node keeps the code paths alive | |
| if (sink === 123_456_789) console.log("ignore:", sink); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
You can use this feature to avoid
next()call overhead: