11const EventEmitter = require ( 'events' ) ;
2- const { callbackify, promisify } = require ( 'util' ) ;
2+ const { callbackify } = require ( 'util' ) ;
33const IORedis = require ( 'ioredis' ) ;
44const { jsutil } = require ( 'arsenal' ) ;
55const BackOff = require ( 'backo' ) ;
@@ -15,6 +15,49 @@ const moduleLogger = new LoggerContext({
1515const COMMAND_TIMEOUT = 10000 ;
1616const CONNECTION_TIMEOUT = 30000 ;
1717
18+ /**
19+ * Promisifies a function. If the function already returns a promise,
20+ * it returns it as is. Handles both callbacks and promises.
21+ *
22+ * @param {Function } originalFn The function to promisify.
23+ * @returns {Function } A function that returns a promise.
24+ */
25+ function flexiblePromisify ( originalFn ) {
26+ // Check if the function provides a custom promise-based version.
27+ // This is the same mechanism Node's util.promisify uses.
28+ const customPromisified = originalFn [ Symbol . for ( 'nodejs.util.promisify.custom' ) ] ;
29+ if ( typeof customPromisified === 'function' ) {
30+ return customPromisified ;
31+ }
32+
33+ // Return the new promise-based wrapper function.
34+ return function flexiblePromisifiedWrapper ( ...args ) {
35+ const thisCtx = this ;
36+
37+ return new Promise ( ( resolve , reject ) => {
38+ // 1. Callback will be used if `originalFn` is a callback-style function.
39+ function callback ( err , result ) {
40+ if ( err ) {
41+ return reject ( err ) ;
42+ }
43+ return resolve ( result ) ;
44+ }
45+
46+ // 2. Call the originalFn, with user's args AND our custom callback.
47+ const potentialPromise = originalFn . apply ( thisCtx , [ ...args , callback ] ) ;
48+
49+ // 3. If originalFn returned a promise, we use its result and ignore the callback.
50+ if ( potentialPromise && typeof potentialPromise . then === 'function' ) {
51+ // The function returned a promise. We'll trust it as the source of truth.
52+ potentialPromise . then ( resolve , reject ) ;
53+ }
54+
55+ // If the function did NOT return a promise (i.e., it's a standard callback function),
56+ // then our promise is already wired up to be resolved or rejected by the `callback` we passed in.
57+ } ) ;
58+ } ;
59+ }
60+
1861/**
1962* Creates a new Redis client instance
2063* @param {object } conf - redis configuration
@@ -201,7 +244,9 @@ class RedisClient extends EventEmitter {
201244 if ( callback !== undefined ) {
202245 // If a callback is provided `func` is assumed to also take a callback
203246 // and is converted to a promise using promisify
204- return callbackify ( this . _call . bind ( this ) ) ( promisify ( func ) , callback ) ;
247+ // Note (DEP0174): flexiblePromisify avoids promisifying a function that already returns a promise
248+ // With redisClientV2 func returns a promise even if there is a callback
249+ return callbackify ( this . _call . bind ( this ) ) ( flexiblePromisify ( func ) , callback ) ;
205250 }
206251 return this . _call ( func ) ;
207252 }
0 commit comments