Repository: denoland/deno
Pull request: fix(ext/node): support setImmediate promisify.custom

- Add dedicated `setImmediatePromise` function in `timers.ts`
- Add `promisify.custom` property to `setImmediate` that returns `setImmediatePromise`
- Update `promises.setImmediate` to use `setImmediatePromise` directly
- Bump `node_shim` to 0.3.0 (includes fix for `require` availability in eval context)
- Enable `test-timers-immediate-promisified.js` node_compat test


Changed files:
filename: Cargo.lock
patch: @@ -6356,9 +6356,9 @@ dependencies = [
 
 [[package]]
 name = "node_shim"
-version = "0.2.0"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c0c15839a684a17f9f07e62811a8989ba204e8b0f011859d5d0f73a4667d82b"
+checksum = "c645112fb6db1e07cbf7c5f588b671e8239b5e7b19555ef9fb8d6f726cc4928b"
 dependencies = [
  "serde_json",
 ]

filename: Cargo.toml
patch: @@ -213,7 +213,7 @@ memchr = "2.7.4"
 mime = "0.3.16"
 monch = "=0.5.0"
 netif = "0.1.6"
-node_shim = "0.2.0"
+node_shim = "0.3.0"
 notify = "=6.1.1"
 num-bigint = { version = "0.4", features = ["rand"] }
 num-bigint-dig = "0.8.2"

filename: ext/node/ops/node_cli_parser.rs
patch: @@ -206,13 +206,10 @@ mod tests {
     // Eval code should be wrapped for child_process
     assert!(result.deno_args.contains(&"eval".to_string()));
     // Note: deno eval has implicit permissions, so -A is not added
-    // The wrapped code should contain vm.runInThisContext
-    assert!(
-      result
-        .deno_args
-        .iter()
-        .any(|a| a.contains("vm.runInThisContext"))
-    );
+    // The wrapped code should contain process.getBuiltinModule("vm").runInThisContext
+    assert!(result.deno_args.iter().any(|a| {
+      a.contains(r#"process.getBuiltinModule("vm").runInThisContext"#)
+    }));
   }
 
   #[test]
@@ -339,7 +336,9 @@ mod tests {
   #[test]
   fn test_wrap_eval_code() {
     let wrapped = wrap_eval_code("console.log(42)");
-    assert!(wrapped.contains("vm.runInThisContext"));
+    assert!(
+      wrapped.contains(r#"process.getBuiltinModule("vm").runInThisContext"#)
+    );
     assert!(wrapped.contains("process.getBuiltinModule"));
     assert!(wrapped.contains("\"console.log(42)\""));
   }

filename: ext/node/polyfills/timers.ts
patch: @@ -156,6 +156,72 @@ export function setImmediate(
   return new Immediate(cb, ...new SafeArrayIterator(args));
 }
 
+function setImmediatePromise<T = void>(
+  value?: T,
+  options: TimerOptions = kEmptyObject,
+): Promise<T> {
+  try {
+    validateObject(options, "options");
+
+    if (typeof options?.signal !== "undefined") {
+      validateAbortSignal(options.signal, "options.signal");
+    }
+
+    if (typeof options?.ref !== "undefined") {
+      validateBoolean(options.ref, "options.ref");
+    }
+  } catch (err) {
+    return PromiseReject(err);
+  }
+
+  const { signal, ref = true } = options;
+
+  if (signal?.aborted) {
+    return PromiseReject(new AbortError(undefined, { cause: signal.reason }));
+  }
+
+  let oncancel: EventListenerOrEventListenerObject | undefined;
+  const { promise, resolve, reject } = PromiseWithResolvers();
+  const immediate = new Immediate(() => resolve(value));
+  if (!ref) {
+    immediate.unref();
+  }
+  if (signal) {
+    oncancel = FunctionPrototypeBind(
+      cancelListenerHandler,
+      immediate,
+      clearImmediate,
+      reject,
+      signal,
+    );
+
+    signal.addEventListener("abort", oncancel, {
+      __proto__: null,
+      [kResistStopPropagation]: true,
+    });
+  }
+
+  return oncancel !== undefined
+    ? SafePromisePrototypeFinally(
+      promise,
+      () => signal!.removeEventListener("abort", oncancel),
+    )
+    : promise;
+}
+
+ObjectDefineProperty(setImmediatePromise, "name", {
+  __proto__: null,
+  value: "setImmediate",
+});
+
+ObjectDefineProperty(setImmediate, promisify.custom, {
+  __proto__: null,
+  enumerable: true,
+  get() {
+    return setImmediatePromise;
+  },
+});
+
 export function clearImmediate(immediate: Immediate) {
   if (!immediate?._onImmediate || immediate._destroyed) {
     return;
@@ -254,7 +320,7 @@ async function* setIntervalAsync(
 
 export const promises = {
   setTimeout: setTimeoutPromise,
-  setImmediate: promisify(setImmediate),
+  setImmediate: setImmediatePromise,
   setInterval: setIntervalAsync,
 };
 

filename: tests/node_compat/config.jsonc
patch: @@ -1330,6 +1330,7 @@
     "parallel/test-timers-clear-timeout-interval-equivalent.js": {},
     "parallel/test-timers-clearImmediate-als.js": {},
     "parallel/test-timers-clearImmediate.js": {},
+    "parallel/test-timers-immediate-promisified.js": {},
     "parallel/test-timers-immediate-unref-nested-once.js": {},
     "parallel/test-timers-immediate-unref-simple.js": {},
     "parallel/test-timers-immediate-unref.js": {},