diff --git a/lib/internal/perf/timerify.js b/lib/internal/perf/timerify.js index f9e9b8a39d99..045fd471c1e9 100644 --- a/lib/internal/perf/timerify.js +++ b/lib/internal/perf/timerify.js @@ -52,6 +52,11 @@ function processComplete(name, start, args, histogram) { enqueue(entry); } +function onThenableFulfilled(name, start, args, histogram, value) { + processComplete(name, start, args, histogram); + return value; +} + function timerify(fn, options = kEmptyObject) { validateFunction(fn, 'fn'); @@ -74,10 +79,15 @@ function timerify(fn, options = kEmptyObject) { const result = isConstructorCall ? ReflectConstruct(fn, args, fn) : ReflectApply(fn, this, args); - if (!isConstructorCall && typeof result?.finally === 'function') { - return result.finally( + if (!isConstructorCall && typeof result?.then === 'function') { + // Only record on fulfillment, not rejection, so a function that + // returns a rejected thenable behaves the same as one that throws + // synchronously (neither reaches `processComplete()`). A plain + // `.finally()` would record in both cases, and thenables are only + // required to implement `then()`. + return result.then( FunctionPrototypeBind( - processComplete, + onThenableFulfilled, result, fn.name, start, diff --git a/test/parallel/test-perf-hooks-timerify-async-error.js b/test/parallel/test-perf-hooks-timerify-async-error.js new file mode 100644 index 000000000000..662b1275a31f --- /dev/null +++ b/test/parallel/test-perf-hooks-timerify-async-error.js @@ -0,0 +1,22 @@ +// Test that a timerified function which returns a rejected promise behaves +// the same as one that throws synchronously: no performance timeline entry +// and no histogram record. + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { timerify, PerformanceObserver, createHistogram } = require('perf_hooks'); + +const obs = new PerformanceObserver(common.mustNotCall()); +obs.observe({ entryTypes: ['function'] }); + +const histogram = createHistogram(); +const n = timerify(async () => { + throw new Error('test'); +}, { histogram }); + +assert.rejects(n(), /^Error: test$/).then(common.mustCall(() => { + assert.strictEqual(histogram.count, 0); + obs.disconnect(); +}));