From e3c5a446e453b78589bf141d7737408346b8da57 Mon Sep 17 00:00:00 2001
From: ZiJian Liu <Lxxyxzj@gmail.com>
Date: Wed, 23 Dec 2020 19:22:00 +0800
Subject: [PATCH] lib: refactor to use validateCallback

---
 lib/_tls_wrap.js                     |  7 +++---
 lib/dns.js                           | 35 ++++++++++++++--------------
 lib/fs.js                            | 19 ++++++---------
 lib/inspector.js                     | 10 ++++----
 lib/internal/crypto/diffiehellman.js |  8 +++----
 lib/internal/crypto/hkdf.js          |  5 ++--
 lib/internal/crypto/keygen.js        |  8 +++----
 lib/internal/crypto/pbkdf2.js        |  5 ++--
 lib/internal/crypto/random.js        | 19 ++++++++-------
 lib/internal/crypto/scrypt.js        |  5 ++--
 lib/internal/fs/dir.js               | 15 +++++-------
 lib/internal/http2/compat.js         |  9 +++----
 lib/internal/http2/core.js           | 26 ++++++++++-----------
 lib/internal/process/task_queues.js  |  6 ++---
 lib/internal/stream_base_commons.js  | 10 +++-----
 lib/internal/streams/pipeline.js     |  6 ++---
 lib/internal/timers.js               | 10 ++++----
 lib/internal/url.js                  |  7 +++---
 lib/perf_hooks.js                    |  7 +++---
 lib/readline.js                      | 22 ++++++++++-------
 20 files changed, 114 insertions(+), 125 deletions(-)

diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js
index adbefa4839826d..1447253bf73224 100644
--- a/lib/_tls_wrap.js
+++ b/lib/_tls_wrap.js
@@ -67,7 +67,6 @@ const { connResetException, codes } = require('internal/errors');
 const {
   ERR_INVALID_ARG_TYPE,
   ERR_INVALID_ARG_VALUE,
-  ERR_INVALID_CALLBACK,
   ERR_MULTIPLE_CALLBACK,
   ERR_SOCKET_CLOSED,
   ERR_TLS_DH_PARAM_SIZE,
@@ -85,6 +84,7 @@ const {
   getAllowUnauthorized,
 } = require('internal/options');
 const {
+  validateCallback,
   validateString,
   validateBuffer,
   validateUint32
@@ -825,8 +825,9 @@ TLSSocket.prototype._init = function(socket, wrap) {
 TLSSocket.prototype.renegotiate = function(options, callback) {
   if (options === null || typeof options !== 'object')
     throw new ERR_INVALID_ARG_TYPE('options', 'Object', options);
-  if (callback !== undefined && typeof callback !== 'function')
-    throw new ERR_INVALID_CALLBACK(callback);
+  if (callback !== undefined) {
+    validateCallback(callback);
+  }
 
   debug('%s renegotiate()',
         this._tlsOptions.isServer ? 'server' : 'client',
diff --git a/lib/dns.js b/lib/dns.js
index 0c1b259d7341ee..1116c73f670438 100644
--- a/lib/dns.js
+++ b/lib/dns.js
@@ -45,10 +45,10 @@ const {
 const {
   ERR_INVALID_ARG_TYPE,
   ERR_INVALID_ARG_VALUE,
-  ERR_INVALID_CALLBACK,
   ERR_MISSING_ARGS,
 } = errors.codes;
 const {
+  validateCallback,
   validatePort,
   validateString,
   validateOneOf,
@@ -101,20 +101,24 @@ function lookup(hostname, options, callback) {
   // Parse arguments
   if (hostname && typeof hostname !== 'string') {
     throw new ERR_INVALID_ARG_TYPE('hostname', 'string', hostname);
-  } else if (typeof options === 'function') {
+  }
+
+  if (typeof options === 'function') {
     callback = options;
     family = 0;
-  } else if (typeof callback !== 'function') {
-    throw new ERR_INVALID_CALLBACK(callback);
-  } else if (options !== null && typeof options === 'object') {
-    hints = options.hints >>> 0;
-    family = options.family >>> 0;
-    all = options.all === true;
-    verbatim = options.verbatim === true;
-
-    validateHints(hints);
   } else {
-    family = options >>> 0;
+    validateCallback(callback);
+
+    if (options !== null && typeof options === 'object') {
+      hints = options.hints >>> 0;
+      family = options.family >>> 0;
+      all = options.all === true;
+      verbatim = options.verbatim === true;
+
+      validateHints(hints);
+    } else {
+      family = options >>> 0;
+    }
   }
 
   validateOneOf(family, 'family', [0, 4, 6]);
@@ -177,8 +181,7 @@ function lookupService(address, port, callback) {
 
   validatePort(port);
 
-  if (typeof callback !== 'function')
-    throw new ERR_INVALID_CALLBACK(callback);
+  validateCallback(callback);
 
   port = +port;
 
@@ -217,9 +220,7 @@ function resolver(bindingName) {
     }
 
     validateString(name, 'name');
-    if (typeof callback !== 'function') {
-      throw new ERR_INVALID_CALLBACK(callback);
-    }
+    validateCallback(callback);
 
     const req = new QueryReqWrap();
     req.bindingName = bindingName;
diff --git a/lib/fs.js b/lib/fs.js
index 93e875311e08a6..918762877e6f99 100644
--- a/lib/fs.js
+++ b/lib/fs.js
@@ -75,7 +75,6 @@ const {
     ERR_FS_FILE_TOO_LARGE,
     ERR_INVALID_ARG_VALUE,
     ERR_INVALID_ARG_TYPE,
-    ERR_INVALID_CALLBACK,
     ERR_FEATURE_UNAVAILABLE_ON_PLATFORM
   },
   hideStackFrames,
@@ -125,6 +124,7 @@ const {
   isUint32,
   parseFileMode,
   validateBuffer,
+  validateCallback,
   validateInteger,
   validateInt32
 } = require('internal/validators');
@@ -170,19 +170,16 @@ function showTruncateDeprecation() {
 }
 
 function maybeCallback(cb) {
-  if (typeof cb === 'function')
-    return cb;
+  validateCallback(cb);
 
-  throw new ERR_INVALID_CALLBACK(cb);
+  return cb;
 }
 
 // Ensure that callbacks run in the global context. Only use this function
 // for callbacks that are passed to the binding layer, callbacks that are
 // invoked from JS already run in the proper scope.
 function makeCallback(cb) {
-  if (typeof cb !== 'function') {
-    throw new ERR_INVALID_CALLBACK(cb);
-  }
+  validateCallback(cb);
 
   return (...args) => cb(...args);
 }
@@ -191,9 +188,7 @@ function makeCallback(cb) {
 // an optimization, since the data passed back to the callback needs to be
 // transformed anyway.
 function makeStatsCallback(cb) {
-  if (typeof cb !== 'function') {
-    throw new ERR_INVALID_CALLBACK(cb);
-  }
+  validateCallback(cb);
 
   return (err, stats) => {
     if (err) return cb(err);
@@ -2014,8 +2009,8 @@ function copyFile(src, dest, mode, callback) {
   if (typeof mode === 'function') {
     callback = mode;
     mode = 0;
-  } else if (typeof callback !== 'function') {
-    throw new ERR_INVALID_CALLBACK(callback);
+  } else {
+    validateCallback(callback);
   }
 
   src = getValidatedPath(src, 'src');
diff --git a/lib/inspector.js b/lib/inspector.js
index 007822ffa20f0d..30b2e6407ce659 100644
--- a/lib/inspector.js
+++ b/lib/inspector.js
@@ -17,7 +17,6 @@ const {
   ERR_INSPECTOR_NOT_ACTIVE,
   ERR_INSPECTOR_NOT_WORKER,
   ERR_INVALID_ARG_TYPE,
-  ERR_INVALID_CALLBACK
 } = require('internal/errors').codes;
 
 const { hasInspector } = internalBinding('config');
@@ -26,7 +25,10 @@ if (!hasInspector)
 
 const EventEmitter = require('events');
 const { queueMicrotask } = require('internal/process/task_queues');
-const { validateString } = require('internal/validators');
+const {
+  validateCallback,
+  validateString,
+} = require('internal/validators');
 const { isMainThread } = require('worker_threads');
 
 const {
@@ -100,8 +102,8 @@ class Session extends EventEmitter {
     if (params && typeof params !== 'object') {
       throw new ERR_INVALID_ARG_TYPE('params', 'Object', params);
     }
-    if (callback && typeof callback !== 'function') {
-      throw new ERR_INVALID_CALLBACK(callback);
+    if (callback) {
+      validateCallback(callback);
     }
 
     if (!this[connectionSymbol]) {
diff --git a/lib/internal/crypto/diffiehellman.js b/lib/internal/crypto/diffiehellman.js
index 2a8795e378f3e0..a28d61369b1c94 100644
--- a/lib/internal/crypto/diffiehellman.js
+++ b/lib/internal/crypto/diffiehellman.js
@@ -31,11 +31,11 @@ const {
     ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE,
     ERR_INVALID_ARG_TYPE,
     ERR_INVALID_ARG_VALUE,
-    ERR_INVALID_CALLBACK,
   }
 } = require('internal/errors');
 
 const {
+  validateCallback,
   validateInt32,
   validateObject,
   validateString,
@@ -325,8 +325,7 @@ function deriveBitsECDH(name, publicKey, privateKey, callback) {
   validateString(name, 'name');
   validateObject(publicKey, 'publicKey');
   validateObject(privateKey, 'privateKey');
-  if (typeof callback !== 'function')
-    throw new ERR_INVALID_CALLBACK(callback);
+  validateCallback(callback);
   const job = new ECDHBitsJob(kCryptoJobAsync, name, publicKey, privateKey);
   job.ondone = (error, bits) => {
     if (error) return FunctionPrototypeCall(callback, job, error);
@@ -340,8 +339,7 @@ function deriveBitsECDH(name, publicKey, privateKey, callback) {
 function deriveBitsDH(publicKey, privateKey, callback) {
   validateObject(publicKey, 'publicKey');
   validateObject(privateKey, 'privateKey');
-  if (typeof callback !== 'function')
-    throw new ERR_INVALID_CALLBACK(callback);
+  validateCallback(callback);
   const job = new DHBitsJob(kCryptoJobAsync, publicKey, privateKey);
   job.ondone = (error, bits) => {
     if (error) return FunctionPrototypeCall(callback, job, error);
diff --git a/lib/internal/crypto/hkdf.js b/lib/internal/crypto/hkdf.js
index 1b013e5ff1591c..7949fa33d2e077 100644
--- a/lib/internal/crypto/hkdf.js
+++ b/lib/internal/crypto/hkdf.js
@@ -13,6 +13,7 @@ const {
 } = internalBinding('crypto');
 
 const {
+  validateCallback,
   validateInteger,
   validateString,
   validateUint32,
@@ -41,7 +42,6 @@ const {
 
 const {
   codes: {
-    ERR_INVALID_CALLBACK,
     ERR_INVALID_ARG_TYPE,
     ERR_OUT_OF_RANGE,
     ERR_MISSING_OPTION,
@@ -112,8 +112,7 @@ function hkdf(hash, key, salt, info, length, callback) {
     length,
   } = validateParameters(hash, key, salt, info, length));
 
-  if (typeof callback !== 'function')
-    throw new ERR_INVALID_CALLBACK(callback);
+  validateCallback(callback);
 
   const job = new HKDFJob(kCryptoJobAsync, hash, key, salt, info, length);
 
diff --git a/lib/internal/crypto/keygen.js b/lib/internal/crypto/keygen.js
index 5f29a3153b94e0..2b0d58afd663fc 100644
--- a/lib/internal/crypto/keygen.js
+++ b/lib/internal/crypto/keygen.js
@@ -40,6 +40,7 @@ const { customPromisifyArgs } = require('internal/util');
 
 const {
   isUint32,
+  validateCallback,
   validateString,
   validateInteger,
   validateObject,
@@ -50,7 +51,6 @@ const {
   codes: {
     ERR_INCOMPATIBLE_OPTION_PAIR,
     ERR_INVALID_ARG_VALUE,
-    ERR_INVALID_CALLBACK,
     ERR_MISSING_OPTION,
   }
 } = require('internal/errors');
@@ -68,8 +68,7 @@ function generateKeyPair(type, options, callback) {
     callback = options;
     options = undefined;
   }
-  if (typeof callback !== 'function')
-    throw new ERR_INVALID_CALLBACK(callback);
+  validateCallback(callback);
 
   const job = createJob(kCryptoJobAsync, type, options);
 
@@ -353,8 +352,7 @@ function generateKey(type, options, callback) {
     options = undefined;
   }
 
-  if (typeof callback !== 'function')
-    throw new ERR_INVALID_CALLBACK(callback);
+  validateCallback(callback);
 
   const job = generateKeyJob(kCryptoJobAsync, type, options);
 
diff --git a/lib/internal/crypto/pbkdf2.js b/lib/internal/crypto/pbkdf2.js
index 868e0f7fbb214e..98f4efb4f333bf 100644
--- a/lib/internal/crypto/pbkdf2.js
+++ b/lib/internal/crypto/pbkdf2.js
@@ -14,13 +14,13 @@ const {
 } = internalBinding('crypto');
 
 const {
+  validateCallback,
   validateInteger,
   validateUint32,
 } = require('internal/validators');
 
 const {
   ERR_INVALID_ARG_TYPE,
-  ERR_INVALID_CALLBACK,
   ERR_MISSING_OPTION,
 } = require('internal/errors').codes;
 
@@ -41,8 +41,7 @@ function pbkdf2(password, salt, iterations, keylen, digest, callback) {
   ({ password, salt, iterations, keylen, digest } =
     check(password, salt, iterations, keylen, digest));
 
-  if (typeof callback !== 'function')
-    throw new ERR_INVALID_CALLBACK(callback);
+  validateCallback(callback);
 
   const job = new PBKDF2Job(
     kCryptoJobAsync,
diff --git a/lib/internal/crypto/random.js b/lib/internal/crypto/random.js
index dca43647a6635a..93b32b92007908 100644
--- a/lib/internal/crypto/random.js
+++ b/lib/internal/crypto/random.js
@@ -23,12 +23,14 @@ const { kMaxLength } = require('buffer');
 const {
   codes: {
     ERR_INVALID_ARG_TYPE,
-    ERR_INVALID_CALLBACK,
     ERR_OUT_OF_RANGE,
   }
 } = require('internal/errors');
 
-const { validateNumber } = require('internal/validators');
+const {
+  validateNumber,
+  validateCallback,
+} = require('internal/validators');
 
 const {
   isArrayBufferView,
@@ -73,8 +75,9 @@ function assertSize(size, elementSize, offset, length) {
 
 function randomBytes(size, callback) {
   size = assertSize(size, 1, 0, Infinity);
-  if (callback !== undefined && typeof callback !== 'function')
-    throw new ERR_INVALID_CALLBACK(callback);
+  if (callback !== undefined) {
+    validateCallback(callback);
+  }
 
   const buf = new FastBuffer(size);
 
@@ -141,8 +144,8 @@ function randomFill(buf, offset, size, callback) {
   } else if (typeof size === 'function') {
     callback = size;
     size = buf.byteLength - offset;
-  } else if (typeof callback !== 'function') {
-    throw new ERR_INVALID_CALLBACK(callback);
+  } else {
+    validateCallback(callback);
   }
 
   offset = assertOffset(offset, elementSize, buf.byteLength);
@@ -188,8 +191,8 @@ function randomInt(min, max, callback) {
   }
 
   const isSync = typeof callback === 'undefined';
-  if (!isSync && typeof callback !== 'function') {
-    throw new ERR_INVALID_CALLBACK(callback);
+  if (!isSync) {
+    validateCallback(callback);
   }
   if (!NumberIsSafeInteger(min)) {
     throw new ERR_INVALID_ARG_TYPE('min', 'a safe integer', min);
diff --git a/lib/internal/crypto/scrypt.js b/lib/internal/crypto/scrypt.js
index 76549c88289ecc..39305c15d8a4ba 100644
--- a/lib/internal/crypto/scrypt.js
+++ b/lib/internal/crypto/scrypt.js
@@ -14,6 +14,7 @@ const {
 } = internalBinding('crypto');
 
 const {
+  validateCallback,
   validateInteger,
   validateUint32,
 } = require('internal/validators');
@@ -22,7 +23,6 @@ const {
   codes: {
     ERR_CRYPTO_SCRYPT_INVALID_PARAMETER,
     ERR_CRYPTO_SCRYPT_NOT_SUPPORTED,
-    ERR_INVALID_CALLBACK,
   }
 } = require('internal/errors');
 
@@ -50,8 +50,7 @@ function scrypt(password, salt, keylen, options, callback = defaults) {
   const { N, r, p, maxmem } = options;
   ({ password, salt, keylen } = options);
 
-  if (typeof callback !== 'function')
-    throw new ERR_INVALID_CALLBACK(callback);
+  validateCallback(callback);
 
   const job = new ScryptJob(
     kCryptoJobAsync, password, salt, N, r, p, maxmem, keylen);
diff --git a/lib/internal/fs/dir.js b/lib/internal/fs/dir.js
index 6eeeb84bda9d70..e63d09713bdcca 100644
--- a/lib/internal/fs/dir.js
+++ b/lib/internal/fs/dir.js
@@ -18,7 +18,6 @@ const {
   codes: {
     ERR_DIR_CLOSED,
     ERR_DIR_CONCURRENT_OPERATION,
-    ERR_INVALID_CALLBACK,
     ERR_MISSING_ARGS
   }
 } = require('internal/errors');
@@ -32,6 +31,7 @@ const {
   handleErrorFromBinding
 } = require('internal/fs/utils');
 const {
+  validateCallback,
   validateUint32
 } = require('internal/validators');
 
@@ -87,10 +87,10 @@ class Dir {
 
     if (callback === undefined) {
       return this[kDirReadPromisified]();
-    } else if (typeof callback !== 'function') {
-      throw new ERR_INVALID_CALLBACK(callback);
     }
 
+    validateCallback(callback);
+
     if (this[kDirOperationQueue] !== null) {
       ArrayPrototypePush(this[kDirOperationQueue], () => {
         this[kDirReadImpl](maybeSync, callback);
@@ -174,9 +174,7 @@ class Dir {
     }
 
     // callback
-    if (typeof callback !== 'function') {
-      throw new ERR_INVALID_CALLBACK(callback);
-    }
+    validateCallback(callback);
 
     if (this[kDirClosed] === true) {
       process.nextTick(callback, new ERR_DIR_CLOSED());
@@ -236,9 +234,8 @@ ObjectDefineProperty(Dir.prototype, SymbolAsyncIterator, {
 
 function opendir(path, options, callback) {
   callback = typeof options === 'function' ? options : callback;
-  if (typeof callback !== 'function') {
-    throw new ERR_INVALID_CALLBACK(callback);
-  }
+  validateCallback(callback);
+
   path = getValidatedPath(path);
   options = getOptions(options, {
     encoding: 'utf8'
diff --git a/lib/internal/http2/compat.js b/lib/internal/http2/compat.js
index a0251ffbafecd2..e7a0cbb436b1f5 100644
--- a/lib/internal/http2/compat.js
+++ b/lib/internal/http2/compat.js
@@ -44,13 +44,15 @@ const {
     ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED,
     ERR_HTTP2_STATUS_INVALID,
     ERR_INVALID_ARG_VALUE,
-    ERR_INVALID_CALLBACK,
     ERR_INVALID_HTTP_TOKEN,
     ERR_STREAM_WRITE_AFTER_END
   },
   hideStackFrames
 } = require('internal/errors');
-const { validateString } = require('internal/validators');
+const {
+  validateCallback,
+  validateString,
+} = require('internal/validators');
 const {
   kSocket,
   kRequest,
@@ -784,8 +786,7 @@ class Http2ServerResponse extends Stream {
   }
 
   createPushResponse(headers, callback) {
-    if (typeof callback !== 'function')
-      throw new ERR_INVALID_CALLBACK(callback);
+    validateCallback(callback);
     if (this[kState].closed) {
       process.nextTick(callback, new ERR_HTTP2_INVALID_STREAM());
       return;
diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js
index 4fbee39c99ddf2..bc4bdeec5110b8 100644
--- a/lib/internal/http2/core.js
+++ b/lib/internal/http2/core.js
@@ -104,7 +104,6 @@ const {
     ERR_HTTP2_UNSUPPORTED_PROTOCOL,
     ERR_INVALID_ARG_TYPE,
     ERR_INVALID_ARG_VALUE,
-    ERR_INVALID_CALLBACK,
     ERR_INVALID_CHAR,
     ERR_INVALID_HTTP_TOKEN,
     ERR_OUT_OF_RANGE,
@@ -115,12 +114,13 @@ const {
 } = require('internal/errors');
 const {
   isUint32,
+  validateCallback,
   validateInt32,
   validateInteger,
   validateNumber,
   validateString,
   validateUint32,
-  validateAbortSignal,
+  validateAbortSignal
 } = require('internal/validators');
 const fsPromisesInternal = require('internal/fs/promises');
 const { utcDate } = require('internal/http');
@@ -1309,8 +1309,7 @@ class Http2Session extends EventEmitter {
     if (payload && payload.length !== 8) {
       throw new ERR_HTTP2_PING_LENGTH();
     }
-    if (typeof callback !== 'function')
-      throw new ERR_INVALID_CALLBACK(callback);
+    validateCallback(callback);
 
     const cb = pingCallback(callback);
     if (this.connecting || this.closed) {
@@ -1407,8 +1406,9 @@ class Http2Session extends EventEmitter {
     assertIsObject(settings, 'settings');
     validateSettings(settings);
 
-    if (callback && typeof callback !== 'function')
-      throw new ERR_INVALID_CALLBACK(callback);
+    if (callback) {
+      validateCallback(callback);
+    }
     debugSessionObj(this, 'sending settings');
 
     this[kState].pendingAck++;
@@ -2204,8 +2204,9 @@ class Http2Stream extends Duplex {
   close(code = NGHTTP2_NO_ERROR, callback) {
     validateInteger(code, 'code', 0, kMaxInt);
 
-    if (callback !== undefined && typeof callback !== 'function')
-      throw new ERR_INVALID_CALLBACK(callback);
+    if (callback !== undefined) {
+      validateCallback(callback);
+    }
 
     if (this.closed)
       return;
@@ -2607,8 +2608,7 @@ class ServerHttp2Stream extends Http2Stream {
       options = undefined;
     }
 
-    if (typeof callback !== 'function')
-      throw new ERR_INVALID_CALLBACK(callback);
+    validateCallback(callback);
 
     assertIsObject(options, 'options');
     options = { ...options };
@@ -3048,8 +3048,7 @@ class Http2SecureServer extends TLSServer {
   setTimeout(msecs, callback) {
     this.timeout = msecs;
     if (callback !== undefined) {
-      if (typeof callback !== 'function')
-        throw new ERR_INVALID_CALLBACK(callback);
+      validateCallback(callback);
       this.on('timeout', callback);
     }
     return this;
@@ -3076,8 +3075,7 @@ class Http2Server extends NETServer {
   setTimeout(msecs, callback) {
     this.timeout = msecs;
     if (callback !== undefined) {
-      if (typeof callback !== 'function')
-        throw new ERR_INVALID_CALLBACK(callback);
+      validateCallback(callback);
       this.on('timeout', callback);
     }
     return this;
diff --git a/lib/internal/process/task_queues.js b/lib/internal/process/task_queues.js
index 3cf07601a23aa4..4081acae951acd 100644
--- a/lib/internal/process/task_queues.js
+++ b/lib/internal/process/task_queues.js
@@ -34,11 +34,12 @@ const {
   symbols: { async_id_symbol, trigger_async_id_symbol }
 } = require('internal/async_hooks');
 const {
-  ERR_INVALID_CALLBACK,
   ERR_INVALID_ARG_TYPE
 } = require('internal/errors').codes;
 const FixedQueue = require('internal/fixed_queue');
 
+const { validateCallback } = require('internal/validators');
+
 // *Must* match Environment::TickInfo::Fields in src/env.h.
 const kHasTickScheduled = 0;
 
@@ -99,8 +100,7 @@ function processTicksAndRejections() {
 // `nextTick()` will not enqueue any callback when the process is about to
 // exit since the callback would not have a chance to be executed.
 function nextTick(callback) {
-  if (typeof callback !== 'function')
-    throw new ERR_INVALID_CALLBACK(callback);
+  validateCallback(callback);
 
   if (process._exiting)
     return;
diff --git a/lib/internal/stream_base_commons.js b/lib/internal/stream_base_commons.js
index 0dfee0c9c7b619..5254fc1553dd77 100644
--- a/lib/internal/stream_base_commons.js
+++ b/lib/internal/stream_base_commons.js
@@ -17,9 +17,6 @@ const {
 } = internalBinding('stream_wrap');
 const { UV_EOF } = internalBinding('uv');
 const {
-  codes: {
-    ERR_INVALID_CALLBACK
-  },
   errnoException
 } = require('internal/errors');
 const { owner_symbol } = require('internal/async_hooks').symbols;
@@ -30,6 +27,7 @@ const {
 } = require('internal/timers');
 const { isUint8Array } = require('internal/util/types');
 const { clearTimeout } = require('timers');
+const { validateCallback } = require('internal/validators');
 
 const kMaybeDestroy = Symbol('kMaybeDestroy');
 const kUpdateTimer = Symbol('kUpdateTimer');
@@ -259,8 +257,7 @@ function setStreamTimeout(msecs, callback) {
 
   if (msecs === 0) {
     if (callback !== undefined) {
-      if (typeof callback !== 'function')
-        throw new ERR_INVALID_CALLBACK(callback);
+      validateCallback(callback);
       this.removeListener('timeout', callback);
     }
   } else {
@@ -268,8 +265,7 @@ function setStreamTimeout(msecs, callback) {
     if (this[kSession]) this[kSession][kUpdateTimer]();
 
     if (callback !== undefined) {
-      if (typeof callback !== 'function')
-        throw new ERR_INVALID_CALLBACK(callback);
+      validateCallback(callback);
       this.once('timeout', callback);
     }
   }
diff --git a/lib/internal/streams/pipeline.js b/lib/internal/streams/pipeline.js
index 89561433c8c132..8e5e117c9cf3c5 100644
--- a/lib/internal/streams/pipeline.js
+++ b/lib/internal/streams/pipeline.js
@@ -17,11 +17,12 @@ const destroyImpl = require('internal/streams/destroy');
 const {
   ERR_INVALID_ARG_TYPE,
   ERR_INVALID_RETURN_VALUE,
-  ERR_INVALID_CALLBACK,
   ERR_MISSING_ARGS,
   ERR_STREAM_DESTROYED
 } = require('internal/errors').codes;
 
+const { validateCallback } = require('internal/validators');
+
 let EE;
 let PassThrough;
 let Readable;
@@ -73,8 +74,7 @@ function popCallback(streams) {
   // Streams should never be an empty array. It should always contain at least
   // a single stream. Therefore optimize for the average case instead of
   // checking for length === 0 as well.
-  if (typeof streams[streams.length - 1] !== 'function')
-    throw new ERR_INVALID_CALLBACK(streams[streams.length - 1]);
+  validateCallback(streams[streams.length - 1]);
   return streams.pop();
 }
 
diff --git a/lib/internal/timers.js b/lib/internal/timers.js
index 5009572f90f265..a5b5c10010e95b 100644
--- a/lib/internal/timers.js
+++ b/lib/internal/timers.js
@@ -108,10 +108,12 @@ const trigger_async_id_symbol = Symbol('triggerId');
 const kHasPrimitive = Symbol('kHasPrimitive');
 
 const {
-  ERR_INVALID_CALLBACK,
   ERR_OUT_OF_RANGE
 } = require('internal/errors').codes;
-const { validateNumber } = require('internal/validators');
+const {
+  validateCallback,
+  validateNumber,
+} = require('internal/validators');
 
 const L = require('internal/linkedlist');
 const PriorityQueue = require('internal/priority_queue');
@@ -368,9 +370,7 @@ function insert(item, msecs, start = getLibuvNow()) {
 
 function setUnrefTimeout(callback, after) {
   // Type checking identical to setTimeout()
-  if (typeof callback !== 'function') {
-    throw new ERR_INVALID_CALLBACK(callback);
-  }
+  validateCallback(callback);
 
   const timer = new Timeout(callback, after, undefined, false, false);
   insert(timer, timer._idleTimeout);
diff --git a/lib/internal/url.js b/lib/internal/url.js
index 9c883b6268737c..4f3df89db91dc3 100644
--- a/lib/internal/url.js
+++ b/lib/internal/url.js
@@ -30,7 +30,6 @@ const {
   ERR_ARG_NOT_ITERABLE,
   ERR_INVALID_ARG_TYPE,
   ERR_INVALID_ARG_VALUE,
-  ERR_INVALID_CALLBACK,
   ERR_INVALID_FILE_URL_HOST,
   ERR_INVALID_FILE_URL_PATH,
   ERR_INVALID_THIS,
@@ -51,6 +50,8 @@ const {
 } = require('internal/constants');
 const path = require('path');
 
+const { validateCallback } = require('internal/validators');
+
 // Lazy loaded for startup performance.
 let querystring;
 
@@ -1106,9 +1107,7 @@ defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', {
     if (!this || !this[searchParams] || this[searchParams][searchParams]) {
       throw new ERR_INVALID_THIS('URLSearchParams');
     }
-    if (typeof callback !== 'function') {
-      throw new ERR_INVALID_CALLBACK(callback);
-    }
+    validateCallback(callback);
 
     let list = this[searchParams];
 
diff --git a/lib/perf_hooks.js b/lib/perf_hooks.js
index 7429db38fd77ef..6733992b9fae39 100644
--- a/lib/perf_hooks.js
+++ b/lib/perf_hooks.js
@@ -56,7 +56,6 @@ const L = require('internal/linkedlist');
 const kInspect = require('internal/util').customInspectSymbol;
 
 const {
-  ERR_INVALID_CALLBACK,
   ERR_INVALID_ARG_TYPE,
   ERR_INVALID_ARG_VALUE,
   ERR_VALID_PERFORMANCE_ENTRY_TYPE,
@@ -68,6 +67,8 @@ const {
   kHandle,
 } = require('internal/histogram');
 
+const { validateCallback } = require('internal/validators');
+
 const { setImmediate } = require('timers');
 const kCallback = Symbol('callback');
 const kTypes = Symbol('types');
@@ -341,9 +342,7 @@ class PerformanceObserverEntryList {
 
 class PerformanceObserver {
   constructor(callback) {
-    if (typeof callback !== 'function') {
-      throw new ERR_INVALID_CALLBACK(callback);
-    }
+    validateCallback(callback);
     ObjectDefineProperties(this, {
       [kTypes]: {
         enumerable: false,
diff --git a/lib/readline.js b/lib/readline.js
index 352b7ed64f06ba..aed065ae0af3bc 100644
--- a/lib/readline.js
+++ b/lib/readline.js
@@ -63,10 +63,10 @@ const {
 
 const {
   ERR_INVALID_ARG_VALUE,
-  ERR_INVALID_CALLBACK,
   ERR_INVALID_CURSOR_POS,
 } = require('internal/errors').codes;
 const {
+  validateCallback,
   validateString,
   validateUint32,
 } = require('internal/validators');
@@ -1228,8 +1228,9 @@ function emitKeypressEvents(stream, iface = {}) {
  */
 
 function cursorTo(stream, x, y, callback) {
-  if (callback !== undefined && typeof callback !== 'function')
-    throw new ERR_INVALID_CALLBACK(callback);
+  if (callback !== undefined) {
+    validateCallback(callback);
+  }
 
   if (typeof y === 'function') {
     callback = y;
@@ -1259,8 +1260,9 @@ function cursorTo(stream, x, y, callback) {
  */
 
 function moveCursor(stream, dx, dy, callback) {
-  if (callback !== undefined && typeof callback !== 'function')
-    throw new ERR_INVALID_CALLBACK(callback);
+  if (callback !== undefined) {
+    validateCallback(callback);
+  }
 
   if (stream == null || !(dx || dy)) {
     if (typeof callback === 'function')
@@ -1293,8 +1295,9 @@ function moveCursor(stream, dx, dy, callback) {
  */
 
 function clearLine(stream, dir, callback) {
-  if (callback !== undefined && typeof callback !== 'function')
-    throw new ERR_INVALID_CALLBACK(callback);
+  if (callback !== undefined) {
+    validateCallback(callback);
+  }
 
   if (stream === null || stream === undefined) {
     if (typeof callback === 'function')
@@ -1315,8 +1318,9 @@ function clearLine(stream, dir, callback) {
  */
 
 function clearScreenDown(stream, callback) {
-  if (callback !== undefined && typeof callback !== 'function')
-    throw new ERR_INVALID_CALLBACK(callback);
+  if (callback !== undefined) {
+    validateCallback(callback);
+  }
 
   if (stream === null || stream === undefined) {
     if (typeof callback === 'function')