From 02c65fad45898b79ef9614319061d19d24cfb9ce Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Thu, 1 Feb 2024 08:51:10 +0530 Subject: fix(node): `util.callbackify` (#22200) Fixes https://github.com/denoland/deno/issues/22180 Matches the Node.js implementation more closely. Removed types, they do not help just make it harder to debug with stack traces. --- ext/node/polyfills/_util/_util_callbackify.js | 91 +++++++++++++++++ ext/node/polyfills/_util/_util_callbackify.ts | 142 -------------------------- ext/node/polyfills/util.ts | 2 +- 3 files changed, 92 insertions(+), 143 deletions(-) create mode 100644 ext/node/polyfills/_util/_util_callbackify.js delete mode 100644 ext/node/polyfills/_util/_util_callbackify.ts (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/_util/_util_callbackify.js b/ext/node/polyfills/_util/_util_callbackify.js new file mode 100644 index 000000000..cb30f7915 --- /dev/null +++ b/ext/node/polyfills/_util/_util_callbackify.js @@ -0,0 +1,91 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// +// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// These are simplified versions of the "real" errors in Node. + +import { primordials } from "ext:core/mod.js"; +const { + ArrayPrototypePop, + Error, + FunctionPrototypeBind, + ReflectApply, + ObjectDefineProperties, + ObjectGetOwnPropertyDescriptors, + ObjectSetPrototypeOf, + ObjectValues, + PromisePrototypeThen, +} = primordials; + +import { nextTick } from "ext:deno_node/_next_tick.ts"; +import { validateFunction } from "ext:deno_node/internal/validators.mjs"; + +class NodeFalsyValueRejectionError extends Error { + code = "ERR_FALSY_VALUE_REJECTION"; + constructor(reason) { + super("Promise was rejected with falsy value"); + this.reason = reason; + } +} + +function callbackify(original) { + validateFunction(original, "original"); + + // We DO NOT return the promise as it gives the user a false sense that + // the promise is actually somehow related to the callback's execution + // and that the callback throwing will reject the promise. + function callbackified(...args) { + const maybeCb = ArrayPrototypePop(args); + validateFunction(maybeCb, "last argument"); + const cb = FunctionPrototypeBind(maybeCb, this); + // In true node style we process the callback on `nextTick` with all the + // implications (stack, `uncaughtException`, `async_hooks`) + PromisePrototypeThen( + ReflectApply(original, this, args), + (ret) => nextTick(cb, null, ret), + (rej) => { + rej = rej || new NodeFalsyValueRejectionError(rej); + return nextTick(cb, rej); + }, + ); + } + + const descriptors = ObjectGetOwnPropertyDescriptors(original); + // It is possible to manipulate a functions `length` or `name` property. This + // guards against the manipulation. + if (typeof descriptors.length.value === "number") { + descriptors.length.value++; + } + if (typeof descriptors.name.value === "string") { + descriptors.name.value += "Callbackified"; + } + const propertiesValues = ObjectValues(descriptors); + for (let i = 0; i < propertiesValues.length; i++) { + // We want to use null-prototype objects to not rely on globally mutable + // %Object.prototype%. + ObjectSetPrototypeOf(propertiesValues[i], null); + } + ObjectDefineProperties(callbackified, descriptors); + return callbackified; +} + +export { callbackify }; diff --git a/ext/node/polyfills/_util/_util_callbackify.ts b/ext/node/polyfills/_util/_util_callbackify.ts deleted file mode 100644 index d6b54486d..000000000 --- a/ext/node/polyfills/_util/_util_callbackify.ts +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -// -// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -// These are simplified versions of the "real" errors in Node. - -import { primordials } from "ext:core/mod.js"; -const { - ArrayPrototypePop, - ReflectApply, - Error, - FunctionPrototypeBind, - ObjectDefineProperties, - ObjectGetOwnPropertyDescriptors, - PromisePrototypeThen, - TypeError, -} = primordials; - -import { nextTick } from "ext:deno_node/_next_tick.ts"; - -class NodeFalsyValueRejectionError extends Error { - public reason: unknown; - public code = "ERR_FALSY_VALUE_REJECTION"; - constructor(reason: unknown) { - super("Promise was rejected with falsy value"); - this.reason = reason; - } -} -class NodeInvalidArgTypeError extends TypeError { - public code = "ERR_INVALID_ARG_TYPE"; - constructor(argumentName: string) { - super(`The ${argumentName} argument must be of type function.`); - } -} - -type Callback = - | ((err: Error) => void) - | ((err: null, result: ResultT) => void); - -function callbackify( - fn: () => PromiseLike, -): (callback: Callback) => void; -function callbackify( - fn: (arg: ArgT) => PromiseLike, -): (arg: ArgT, callback: Callback) => void; -function callbackify( - fn: (arg1: Arg1T, arg2: Arg2T) => PromiseLike, -): (arg1: Arg1T, arg2: Arg2T, callback: Callback) => void; -function callbackify( - fn: (arg1: Arg1T, arg2: Arg2T, arg3: Arg3T) => PromiseLike, -): (arg1: Arg1T, arg2: Arg2T, arg3: Arg3T, callback: Callback) => void; -function callbackify( - fn: ( - arg1: Arg1T, - arg2: Arg2T, - arg3: Arg3T, - arg4: Arg4T, - ) => PromiseLike, -): ( - arg1: Arg1T, - arg2: Arg2T, - arg3: Arg3T, - arg4: Arg4T, - callback: Callback, -) => void; -function callbackify( - fn: ( - arg1: Arg1T, - arg2: Arg2T, - arg3: Arg3T, - arg4: Arg4T, - arg5: Arg5T, - ) => PromiseLike, -): ( - arg1: Arg1T, - arg2: Arg2T, - arg3: Arg3T, - arg4: Arg4T, - arg5: Arg5T, - callback: Callback, -) => void; - -function callbackify( - original: (...args: unknown[]) => PromiseLike, -): (...args: unknown[]) => void { - if (typeof original !== "function") { - throw new NodeInvalidArgTypeError('"original"'); - } - - const callbackified = function (this: unknown, ...args: unknown[]) { - const maybeCb = ArrayPrototypePop(args); - if (typeof maybeCb !== "function") { - throw new NodeInvalidArgTypeError("last"); - } - const cb = (...args: unknown[]) => { - ReflectApply(maybeCb, this, args); - }; - PromisePrototypeThen( - ReflectApply(this, args), - (ret: unknown) => { - nextTick(FunctionPrototypeBind(cb, this, null, ret)); - }, - (rej: unknown) => { - rej = rej || new NodeFalsyValueRejectionError(rej); - nextTick(FunctionPrototypeBind(cb, this, rej)); - }, - ); - }; - - const descriptors = ObjectGetOwnPropertyDescriptors(original); - // It is possible to manipulate a functions `length` or `name` property. This - // guards against the manipulation. - if (typeof descriptors.length.value === "number") { - descriptors.length.value++; - } - if (typeof descriptors.name.value === "string") { - descriptors.name.value += "Callbackified"; - } - ObjectDefineProperties(callbackified, descriptors); - return callbackified; -} - -export { callbackify }; diff --git a/ext/node/polyfills/util.ts b/ext/node/polyfills/util.ts index 3d449f8dc..0c150301a 100644 --- a/ext/node/polyfills/util.ts +++ b/ext/node/polyfills/util.ts @@ -28,7 +28,7 @@ const { } = primordials; import { promisify } from "ext:deno_node/internal/util.mjs"; -import { callbackify } from "ext:deno_node/_util/_util_callbackify.ts"; +import { callbackify } from "ext:deno_node/_util/_util_callbackify.js"; import { debuglog } from "ext:deno_node/internal/util/debuglog.ts"; import { format, -- cgit v1.2.3