diff --git a/node.gyp b/node.gyp
index d37be3141bd317..e83050d83df0d9 100644
--- a/node.gyp
+++ b/node.gyp
@@ -612,6 +612,7 @@
         'src/node_dir.h',
         'src/node_errors.h',
         'src/node_file.h',
+        'src/node_file-inl.h',
         'src/node_http2.h',
         'src/node_http2_state.h',
         'src/node_i18n.h',
diff --git a/src/env.cc b/src/env.cc
index c8704cb3be6dce..80a704ebc42a0e 100644
--- a/src/env.cc
+++ b/src/env.cc
@@ -12,6 +12,7 @@
 #include "node_process.h"
 #include "node_v8_platform-inl.h"
 #include "node_worker.h"
+#include "req_wrap-inl.h"
 #include "tracing/agent.h"
 #include "tracing/traced_value.h"
 #include "util-inl.h"
@@ -36,6 +37,7 @@ using v8::HandleScope;
 using v8::Integer;
 using v8::Isolate;
 using v8::Local;
+using v8::MaybeLocal;
 using v8::NewStringType;
 using v8::Number;
 using v8::Object;
diff --git a/src/node_dir.cc b/src/node_dir.cc
index ffef27f4521403..21ca694ab966c9 100644
--- a/src/node_dir.cc
+++ b/src/node_dir.cc
@@ -1,10 +1,11 @@
 #include "node_dir.h"
+#include "node_file-inl.h"
 #include "node_process.h"
+#include "memory_tracker-inl.h"
 #include "util.h"
 
 #include "tracing/trace_event.h"
 
-#include "req_wrap-inl.h"
 #include "string_bytes.h"
 
 #include <fcntl.h>
@@ -85,6 +86,10 @@ DirHandle::~DirHandle() {
   CHECK(closed_);    // We have to be closed at the point
 }
 
+void DirHandle::MemoryInfo(MemoryTracker* tracker) const {
+  tracker->TrackFieldWithSize("dir", sizeof(*dir_));
+}
+
 // Close the directory handle if it hasn't already been closed. A process
 // warning will be emitted using a SetImmediate to avoid calling back to
 // JS during GC. If closing the fd fails at this point, a fatal exception
diff --git a/src/node_dir.h b/src/node_dir.h
index ae6d0eb170d679..caef7a5d309180 100644
--- a/src/node_dir.h
+++ b/src/node_dir.h
@@ -4,8 +4,6 @@
 #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
 
 #include "node_file.h"
-#include "node.h"
-#include "req_wrap-inl.h"
 
 namespace node {
 
@@ -20,16 +18,13 @@ class DirHandle : public AsyncWrap {
   ~DirHandle() override;
 
   static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
-  static void Open(const v8::FunctionCallbackInfo<Value>& args);
-  static void Read(const v8::FunctionCallbackInfo<Value>& args);
-  static void Close(const v8::FunctionCallbackInfo<Value>& args);
+  static void Open(const v8::FunctionCallbackInfo<v8::Value>& args);
+  static void Read(const v8::FunctionCallbackInfo<v8::Value>& args);
+  static void Close(const v8::FunctionCallbackInfo<v8::Value>& args);
 
   inline uv_dir_t* dir() { return dir_; }
 
-  void MemoryInfo(MemoryTracker* tracker) const override {
-    tracker->TrackFieldWithSize("dir", sizeof(*dir_));
-  }
-
+  void MemoryInfo(MemoryTracker* tracker) const override;
   SET_MEMORY_INFO_NAME(DirHandle)
   SET_SELF_SIZE(DirHandle)
 
diff --git a/src/node_file-inl.h b/src/node_file-inl.h
new file mode 100644
index 00000000000000..390f6c7415770f
--- /dev/null
+++ b/src/node_file-inl.h
@@ -0,0 +1,283 @@
+#ifndef SRC_NODE_FILE_INL_H_
+#define SRC_NODE_FILE_INL_H_
+
+#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
+
+#include "node_file.h"
+#include "req_wrap-inl.h"
+
+namespace node {
+namespace fs {
+
+FSContinuationData::FSContinuationData(uv_fs_t* req, int mode, uv_fs_cb done_cb)
+  : done_cb_(done_cb), req_(req), mode_(mode) {
+}
+
+void FSContinuationData::PushPath(std::string&& path) {
+  paths_.emplace_back(std::move(path));
+}
+
+void FSContinuationData::PushPath(const std::string& path) {
+  paths_.push_back(path);
+}
+
+std::string FSContinuationData::PopPath() {
+  CHECK_GT(paths_.size(), 0);
+  std::string path = std::move(paths_.back());
+  paths_.pop_back();
+  return path;
+}
+
+void FSContinuationData::Done(int result) {
+  req_->result = result;
+  done_cb_(req_);
+}
+
+FSReqBase::FSReqBase(Environment* env,
+          v8::Local<v8::Object> req,
+          AsyncWrap::ProviderType type,
+          bool use_bigint)
+  : ReqWrap(env, req, type), use_bigint_(use_bigint) {
+}
+
+void FSReqBase::Init(const char* syscall,
+                     const char* data,
+                     size_t len,
+                     enum encoding encoding) {
+  syscall_ = syscall;
+  encoding_ = encoding;
+
+  if (data != nullptr) {
+    CHECK(!has_data_);
+    buffer_.AllocateSufficientStorage(len + 1);
+    buffer_.SetLengthAndZeroTerminate(len);
+    memcpy(*buffer_, data, len);
+    has_data_ = true;
+  }
+}
+
+FSReqBase::FSReqBuffer&
+FSReqBase::Init(const char* syscall, size_t len, enum encoding encoding) {
+  syscall_ = syscall;
+  encoding_ = encoding;
+
+  buffer_.AllocateSufficientStorage(len + 1);
+  has_data_ = false;  // so that the data does not show up in error messages
+  return buffer_;
+}
+
+FSReqCallback::FSReqCallback(Environment* env,
+                             v8::Local<v8::Object> req, bool use_bigint)
+  : FSReqBase(env, req, AsyncWrap::PROVIDER_FSREQCALLBACK, use_bigint) {}
+
+template <typename NativeT, typename V8T>
+void FillStatsArray(AliasedBufferBase<NativeT, V8T>* fields,
+                    const uv_stat_t* s,
+                    const size_t offset) {
+#define SET_FIELD_WITH_STAT(stat_offset, stat)                               \
+  fields->SetValue(offset + static_cast<size_t>(FsStatsOffset::stat_offset), \
+                   static_cast<NativeT>(stat))
+
+#define SET_FIELD_WITH_TIME_STAT(stat_offset, stat)                          \
+  /* NOLINTNEXTLINE(runtime/int) */                                          \
+  SET_FIELD_WITH_STAT(stat_offset, static_cast<unsigned long>(stat))
+
+  SET_FIELD_WITH_STAT(kDev, s->st_dev);
+  SET_FIELD_WITH_STAT(kMode, s->st_mode);
+  SET_FIELD_WITH_STAT(kNlink, s->st_nlink);
+  SET_FIELD_WITH_STAT(kUid, s->st_uid);
+  SET_FIELD_WITH_STAT(kGid, s->st_gid);
+  SET_FIELD_WITH_STAT(kRdev, s->st_rdev);
+  SET_FIELD_WITH_STAT(kBlkSize, s->st_blksize);
+  SET_FIELD_WITH_STAT(kIno, s->st_ino);
+  SET_FIELD_WITH_STAT(kSize, s->st_size);
+  SET_FIELD_WITH_STAT(kBlocks, s->st_blocks);
+
+  SET_FIELD_WITH_TIME_STAT(kATimeSec, s->st_atim.tv_sec);
+  SET_FIELD_WITH_TIME_STAT(kATimeNsec, s->st_atim.tv_nsec);
+  SET_FIELD_WITH_TIME_STAT(kMTimeSec, s->st_mtim.tv_sec);
+  SET_FIELD_WITH_TIME_STAT(kMTimeNsec, s->st_mtim.tv_nsec);
+  SET_FIELD_WITH_TIME_STAT(kCTimeSec, s->st_ctim.tv_sec);
+  SET_FIELD_WITH_TIME_STAT(kCTimeNsec, s->st_ctim.tv_nsec);
+  SET_FIELD_WITH_TIME_STAT(kBirthTimeSec, s->st_birthtim.tv_sec);
+  SET_FIELD_WITH_TIME_STAT(kBirthTimeNsec, s->st_birthtim.tv_nsec);
+
+#undef SET_FIELD_WITH_TIME_STAT
+#undef SET_FIELD_WITH_STAT
+}
+
+v8::Local<v8::Value> FillGlobalStatsArray(Environment* env,
+                                          const bool use_bigint,
+                                          const uv_stat_t* s,
+                                          const bool second) {
+  const ptrdiff_t offset =
+      second ? static_cast<ptrdiff_t>(FsStatsOffset::kFsStatsFieldsNumber) : 0;
+  if (use_bigint) {
+    auto* const arr = env->fs_stats_field_bigint_array();
+    FillStatsArray(arr, s, offset);
+    return arr->GetJSArray();
+  } else {
+    auto* const arr = env->fs_stats_field_array();
+    FillStatsArray(arr, s, offset);
+    return arr->GetJSArray();
+  }
+}
+
+template <typename AliasedBufferT>
+FSReqPromise<AliasedBufferT>*
+FSReqPromise<AliasedBufferT>::New(Environment* env, bool use_bigint) {
+  v8::Local<v8::Object> obj;
+  if (!env->fsreqpromise_constructor_template()
+           ->NewInstance(env->context())
+           .ToLocal(&obj)) {
+    return nullptr;
+  }
+  v8::Local<v8::Promise::Resolver> resolver;
+  if (!v8::Promise::Resolver::New(env->context()).ToLocal(&resolver) ||
+      obj->Set(env->context(), env->promise_string(), resolver).IsNothing()) {
+    return nullptr;
+  }
+  return new FSReqPromise(env, obj, use_bigint);
+}
+
+template <typename AliasedBufferT>
+FSReqPromise<AliasedBufferT>::~FSReqPromise() {
+  // Validate that the promise was explicitly resolved or rejected.
+  CHECK(finished_);
+}
+
+template <typename AliasedBufferT>
+FSReqPromise<AliasedBufferT>::FSReqPromise(
+    Environment* env,
+    v8::Local<v8::Object> obj,
+    bool use_bigint)
+  : FSReqBase(env, obj, AsyncWrap::PROVIDER_FSREQPROMISE, use_bigint),
+    stats_field_array_(
+        env->isolate(),
+        static_cast<size_t>(FsStatsOffset::kFsStatsFieldsNumber)) {}
+
+template <typename AliasedBufferT>
+void FSReqPromise<AliasedBufferT>::Reject(v8::Local<v8::Value> reject) {
+  finished_ = true;
+  v8::HandleScope scope(env()->isolate());
+  InternalCallbackScope callback_scope(this);
+  v8::Local<v8::Value> value =
+      object()->Get(env()->context(),
+                    env()->promise_string()).ToLocalChecked();
+  v8::Local<v8::Promise::Resolver> resolver = value.As<v8::Promise::Resolver>();
+  USE(resolver->Reject(env()->context(), reject).FromJust());
+}
+
+template <typename AliasedBufferT>
+void FSReqPromise<AliasedBufferT>::Resolve(v8::Local<v8::Value> value) {
+  finished_ = true;
+  v8::HandleScope scope(env()->isolate());
+  InternalCallbackScope callback_scope(this);
+  v8::Local<v8::Value> val =
+      object()->Get(env()->context(),
+                    env()->promise_string()).ToLocalChecked();
+  v8::Local<v8::Promise::Resolver> resolver = val.As<v8::Promise::Resolver>();
+  USE(resolver->Resolve(env()->context(), value).FromJust());
+}
+
+template <typename AliasedBufferT>
+void FSReqPromise<AliasedBufferT>::ResolveStat(const uv_stat_t* stat) {
+  FillStatsArray(&stats_field_array_, stat);
+  Resolve(stats_field_array_.GetJSArray());
+}
+
+template <typename AliasedBufferT>
+void FSReqPromise<AliasedBufferT>::SetReturnValue(
+    const v8::FunctionCallbackInfo<v8::Value>& args) {
+  v8::Local<v8::Value> val =
+      object()->Get(env()->context(),
+                    env()->promise_string()).ToLocalChecked();
+  v8::Local<v8::Promise::Resolver> resolver = val.As<v8::Promise::Resolver>();
+  args.GetReturnValue().Set(resolver->GetPromise());
+}
+
+template <typename AliasedBufferT>
+void FSReqPromise<AliasedBufferT>::MemoryInfo(MemoryTracker* tracker) const {
+  FSReqBase::MemoryInfo(tracker);
+  tracker->TrackField("stats_field_array", stats_field_array_);
+}
+
+FSReqBase* GetReqWrap(Environment* env, v8::Local<v8::Value> value,
+                      bool use_bigint) {
+  if (value->IsObject()) {
+    return Unwrap<FSReqBase>(value.As<v8::Object>());
+  } else if (value->StrictEquals(env->fs_use_promises_symbol())) {
+    if (use_bigint) {
+      return FSReqPromise<AliasedBigUint64Array>::New(env, use_bigint);
+    } else {
+      return FSReqPromise<AliasedFloat64Array>::New(env, use_bigint);
+    }
+  }
+  return nullptr;
+}
+
+// Returns nullptr if the operation fails from the start.
+template <typename Func, typename... Args>
+FSReqBase* AsyncDestCall(Environment* env, FSReqBase* req_wrap,
+                         const v8::FunctionCallbackInfo<v8::Value>& args,
+                         const char* syscall, const char* dest,
+                         size_t len, enum encoding enc, uv_fs_cb after,
+                         Func fn, Args... fn_args) {
+  CHECK_NOT_NULL(req_wrap);
+  req_wrap->Init(syscall, dest, len, enc);
+  int err = req_wrap->Dispatch(fn, fn_args..., after);
+  if (err < 0) {
+    uv_fs_t* uv_req = req_wrap->req();
+    uv_req->result = err;
+    uv_req->path = nullptr;
+    after(uv_req);  // after may delete req_wrap if there is an error
+    req_wrap = nullptr;
+  } else {
+    req_wrap->SetReturnValue(args);
+  }
+
+  return req_wrap;
+}
+
+// Returns nullptr if the operation fails from the start.
+template <typename Func, typename... Args>
+FSReqBase* AsyncCall(Environment* env,
+                     FSReqBase* req_wrap,
+                     const v8::FunctionCallbackInfo<v8::Value>& args,
+                     const char* syscall, enum encoding enc,
+                     uv_fs_cb after, Func fn, Args... fn_args) {
+  return AsyncDestCall(env, req_wrap, args,
+                       syscall, nullptr, 0, enc,
+                       after, fn, fn_args...);
+}
+
+// Template counterpart of SYNC_CALL, except that it only puts
+// the error number and the syscall in the context instead of
+// creating an error in the C++ land.
+// ctx must be checked using value->IsObject() before being passed.
+template <typename Func, typename... Args>
+int SyncCall(Environment* env, v8::Local<v8::Value> ctx,
+             FSReqWrapSync* req_wrap, const char* syscall,
+             Func fn, Args... args) {
+  env->PrintSyncTrace();
+  int err = fn(env->event_loop(), &(req_wrap->req), args..., nullptr);
+  if (err < 0) {
+    v8::Local<v8::Context> context = env->context();
+    v8::Local<v8::Object> ctx_obj = ctx.As<v8::Object>();
+    v8::Isolate* isolate = env->isolate();
+    ctx_obj->Set(context,
+                 env->errno_string(),
+                 v8::Integer::New(isolate, err)).Check();
+    ctx_obj->Set(context,
+                 env->syscall_string(),
+                 OneByteString(isolate, syscall)).Check();
+  }
+  return err;
+}
+
+}  // namespace fs
+}  // namespace node
+
+#endif  // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
+
+#endif  // SRC_NODE_FILE_INL_H_
diff --git a/src/node_file.cc b/src/node_file.cc
index 48b382986c0bb5..577b9c7c4173af 100644
--- a/src/node_file.cc
+++ b/src/node_file.cc
@@ -18,7 +18,8 @@
 // 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.
-#include "node_file.h"
+#include "node_file.h"  // NOLINT(build/include_inline)
+#include "node_file-inl.h"
 #include "aliased_buffer.h"
 #include "memory_tracker-inl.h"
 #include "node_buffer.h"
@@ -107,6 +108,19 @@ inline int64_t GetOffset(Local<Value> value) {
 // functions, and thus does not wrap them properly.
 typedef void(*uv_fs_callback_t)(uv_fs_t*);
 
+
+void FSContinuationData::MemoryInfo(MemoryTracker* tracker) const {
+  tracker->TrackField("paths", paths_);
+}
+
+FileHandleReadWrap::~FileHandleReadWrap() {}
+
+FSReqBase::~FSReqBase() {}
+
+void FSReqBase::MemoryInfo(MemoryTracker* tracker) const {
+  tracker->TrackField("continuation_data", continuation_data_);
+}
+
 // The FileHandle object wraps a file descriptor and will close it on garbage
 // collection if necessary. If that happens, a process warning will be
 // emitted (or a fatal exception will occur if the fd cannot be closed.)
@@ -156,6 +170,16 @@ FileHandle::~FileHandle() {
   CHECK(closed_);    // We have to be closed at the point
 }
 
+int FileHandle::DoWrite(WriteWrap* w,
+                        uv_buf_t* bufs,
+                        size_t count,
+                        uv_stream_t* send_handle) {
+  return UV_ENOSYS;  // Not implemented (yet).
+}
+
+void FileHandle::MemoryInfo(MemoryTracker* tracker) const {
+  tracker->TrackField("current_read", current_read_);
+}
 
 // Close the file descriptor if it hasn't already been closed. A process
 // warning will be emitted using a SetImmediate to avoid calling back to
@@ -225,12 +249,34 @@ FileHandle* FileHandle::CloseReq::file_handle() {
   return Unwrap<FileHandle>(obj);
 }
 
+FileHandle::CloseReq::CloseReq(Environment* env,
+                               Local<Object> obj,
+                               Local<Promise> promise,
+                               Local<Value> ref)
+  : ReqWrap(env, obj, AsyncWrap::PROVIDER_FILEHANDLECLOSEREQ) {
+  promise_.Reset(env->isolate(), promise);
+  ref_.Reset(env->isolate(), ref);
+}
+
+FileHandle::CloseReq::~CloseReq() {
+  uv_fs_req_cleanup(req());
+  promise_.Reset();
+  ref_.Reset();
+}
+
+void FileHandle::CloseReq::MemoryInfo(MemoryTracker* tracker) const {
+  tracker->TrackField("promise", promise_);
+  tracker->TrackField("ref", ref_);
+}
+
+
+
 // Closes this FileHandle asynchronously and returns a Promise that will be
 // resolved when the callback is invoked, or rejects with a UVException if
 // there was a problem closing the fd. This is the preferred mechanism for
 // closing the FD object even tho the object will attempt to close
 // automatically on gc.
-inline MaybeLocal<Promise> FileHandle::ClosePromise() {
+MaybeLocal<Promise> FileHandle::ClosePromise() {
   Isolate* isolate = env()->isolate();
   EscapableHandleScope scope(isolate);
   Local<Context> context = env()->context();
@@ -1157,13 +1203,13 @@ int MKDirpSync(uv_loop_t* loop,
   FSContinuationData continuation_data(req, mode, cb);
   continuation_data.PushPath(std::move(path));
 
-  while (continuation_data.paths.size() > 0) {
+  while (continuation_data.paths().size() > 0) {
     std::string next_path = continuation_data.PopPath();
     int err = uv_fs_mkdir(loop, req, next_path.c_str(), mode, nullptr);
     while (true) {
       switch (err) {
         case 0:
-          if (continuation_data.paths.size() == 0) {
+          if (continuation_data.paths().size() == 0) {
             return 0;
           }
           break;
@@ -1173,7 +1219,7 @@ int MKDirpSync(uv_loop_t* loop,
           if (dirname != next_path) {
             continuation_data.PushPath(std::move(next_path));
             continuation_data.PushPath(std::move(dirname));
-          } else if (continuation_data.paths.size() == 0) {
+          } else if (continuation_data.paths().size() == 0) {
             err = UV_EEXIST;
             continue;
           }
@@ -1188,7 +1234,7 @@ int MKDirpSync(uv_loop_t* loop,
           err = uv_fs_stat(loop, req, next_path.c_str(), nullptr);
           if (err == 0 && !S_ISDIR(req->statbuf.st_mode)) {
             uv_fs_req_cleanup(req);
-            if (orig_err == UV_EEXIST && continuation_data.paths.size() > 0) {
+            if (orig_err == UV_EEXIST && continuation_data.paths().size() > 0) {
               return UV_ENOTDIR;
             }
             return UV_EEXIST;
@@ -1211,14 +1257,14 @@ int MKDirpAsync(uv_loop_t* loop,
                 uv_fs_cb cb) {
   FSReqBase* req_wrap = FSReqBase::from_req(req);
   // on the first iteration of algorithm, stash state information.
-  if (req_wrap->continuation_data == nullptr) {
-    req_wrap->continuation_data =
-        std::make_unique<FSContinuationData>(req, mode, cb);
-    req_wrap->continuation_data->PushPath(std::move(path));
+  if (req_wrap->continuation_data() == nullptr) {
+    req_wrap->set_continuation_data(
+        std::make_unique<FSContinuationData>(req, mode, cb));
+    req_wrap->continuation_data()->PushPath(std::move(path));
   }
 
   // on each iteration of algorithm, mkdir directory on top of stack.
-  std::string next_path = req_wrap->continuation_data->PopPath();
+  std::string next_path = req_wrap->continuation_data()->PopPath();
   int err = uv_fs_mkdir(loop, req, next_path.c_str(), mode,
                         uv_fs_callback_t{[](uv_fs_t* req) {
     FSReqBase* req_wrap = FSReqBase::from_req(req);
@@ -1230,12 +1276,12 @@ int MKDirpAsync(uv_loop_t* loop,
     while (true) {
       switch (err) {
         case 0: {
-          if (req_wrap->continuation_data->paths.size() == 0) {
-            req_wrap->continuation_data->Done(0);
+          if (req_wrap->continuation_data()->paths().size() == 0) {
+            req_wrap->continuation_data()->Done(0);
           } else {
             uv_fs_req_cleanup(req);
             MKDirpAsync(loop, req, path.c_str(),
-                        req_wrap->continuation_data->mode, nullptr);
+                        req_wrap->continuation_data()->mode(), nullptr);
           }
           break;
         }
@@ -1243,19 +1289,19 @@ int MKDirpAsync(uv_loop_t* loop,
           std::string dirname = path.substr(0,
                                             path.find_last_of(kPathSeparator));
           if (dirname != path) {
-            req_wrap->continuation_data->PushPath(std::move(path));
-            req_wrap->continuation_data->PushPath(std::move(dirname));
-          } else if (req_wrap->continuation_data->paths.size() == 0) {
+            req_wrap->continuation_data()->PushPath(std::move(path));
+            req_wrap->continuation_data()->PushPath(std::move(dirname));
+          } else if (req_wrap->continuation_data()->paths().size() == 0) {
             err = UV_EEXIST;
             continue;
           }
           uv_fs_req_cleanup(req);
           MKDirpAsync(loop, req, path.c_str(),
-                      req_wrap->continuation_data->mode, nullptr);
+                      req_wrap->continuation_data()->mode(), nullptr);
           break;
         }
         case UV_EPERM: {
-          req_wrap->continuation_data->Done(err);
+          req_wrap->continuation_data()->Done(err);
           break;
         }
         default:
@@ -1267,14 +1313,14 @@ int MKDirpAsync(uv_loop_t* loop,
             FSReqBase* req_wrap = FSReqBase::from_req(req);
             int err = req->result;
             if (reinterpret_cast<intptr_t>(req->data) == UV_EEXIST &&
-                  req_wrap->continuation_data->paths.size() > 0) {
+                  req_wrap->continuation_data()->paths().size() > 0) {
               if (err == 0 && S_ISDIR(req->statbuf.st_mode)) {
                 Environment* env = req_wrap->env();
                 uv_loop_t* loop = env->event_loop();
                 std::string path = req->path;
                 uv_fs_req_cleanup(req);
                 MKDirpAsync(loop, req, path.c_str(),
-                            req_wrap->continuation_data->mode, nullptr);
+                            req_wrap->continuation_data()->mode(), nullptr);
                 return;
               }
               err = UV_ENOTDIR;
@@ -1282,9 +1328,9 @@ int MKDirpAsync(uv_loop_t* loop,
             // verify that the path pointed to is actually a directory.
             if (err == 0 && !S_ISDIR(req->statbuf.st_mode)) err = UV_EEXIST;
             uv_fs_req_cleanup(req);
-            req_wrap->continuation_data->Done(err);
+            req_wrap->continuation_data()->Done(err);
           }});
-          if (err < 0) req_wrap->continuation_data->Done(err);
+          if (err < 0) req_wrap->continuation_data()->Done(err);
           break;
       }
       break;
diff --git a/src/node_file.h b/src/node_file.h
index 84f4032cc2f6a3..1042baaf8f736b 100644
--- a/src/node_file.h
+++ b/src/node_file.h
@@ -6,113 +6,70 @@
 #include "node.h"
 #include "aliased_buffer.h"
 #include "stream_base.h"
-#include "memory_tracker-inl.h"
-#include "req_wrap-inl.h"
 #include <iostream>
 
 namespace node {
-
-using v8::Context;
-using v8::FunctionCallbackInfo;
-using v8::HandleScope;
-using v8::Local;
-using v8::MaybeLocal;
-using v8::Object;
-using v8::Promise;
-using v8::Undefined;
-using v8::Value;
-
 namespace fs {
 
 // structure used to store state during a complex operation, e.g., mkdirp.
 class FSContinuationData : public MemoryRetainer {
  public:
-  FSContinuationData(uv_fs_t* req, int mode, uv_fs_cb done_cb)
-      : req(req), mode(mode), done_cb(done_cb) {
-  }
-
-  uv_fs_t* req;
-  int mode;
-  std::vector<std::string> paths{};
+  inline FSContinuationData(uv_fs_t* req, int mode, uv_fs_cb done_cb);
 
-  void PushPath(std::string&& path) {
-    paths.emplace_back(std::move(path));
-  }
-
-  void PushPath(const std::string& path) {
-    paths.push_back(path);
-  }
+  inline void PushPath(std::string&& path);
+  inline void PushPath(const std::string& path);
+  inline std::string PopPath();
+  inline void Done(int result);
 
-  std::string PopPath() {
-    CHECK_GT(paths.size(), 0);
-    std::string path = std::move(paths.back());
-    paths.pop_back();
-    return path;
-  }
-
-  void Done(int result) {
-    req->result = result;
-    done_cb(req);
-  }
-
-  void MemoryInfo(MemoryTracker* tracker) const override {
-    tracker->TrackField("paths", paths);
-  }
+  int mode() const { return mode_; }
+  const std::vector<std::string>& paths() const { return paths_; }
 
+  void MemoryInfo(MemoryTracker* tracker) const override;
   SET_MEMORY_INFO_NAME(FSContinuationData)
   SET_SELF_SIZE(FSContinuationData)
 
  private:
-  uv_fs_cb done_cb;
+  uv_fs_cb done_cb_;
+  uv_fs_t* req_;
+  int mode_;
+  std::vector<std::string> paths_;
 };
 
 class FSReqBase : public ReqWrap<uv_fs_t> {
  public:
   typedef MaybeStackBuffer<char, 64> FSReqBuffer;
-  std::unique_ptr<FSContinuationData> continuation_data = nullptr;
-
-  FSReqBase(Environment* env, Local<Object> req, AsyncWrap::ProviderType type,
-            bool use_bigint)
-      : ReqWrap(env, req, type), use_bigint_(use_bigint) {
-  }
 
-  void Init(const char* syscall,
-            const char* data,
-            size_t len,
-            enum encoding encoding) {
-    syscall_ = syscall;
-    encoding_ = encoding;
-
-    if (data != nullptr) {
-      CHECK(!has_data_);
-      buffer_.AllocateSufficientStorage(len + 1);
-      buffer_.SetLengthAndZeroTerminate(len);
-      memcpy(*buffer_, data, len);
-      has_data_ = true;
-    }
-  }
-
-  FSReqBuffer& Init(const char* syscall, size_t len,
-                    enum encoding encoding) {
-    syscall_ = syscall;
-    encoding_ = encoding;
-
-    buffer_.AllocateSufficientStorage(len + 1);
-    has_data_ = false;  // so that the data does not show up in error messages
-    return buffer_;
-  }
-
-  virtual void Reject(Local<Value> reject) = 0;
-  virtual void Resolve(Local<Value> value) = 0;
+  inline FSReqBase(Environment* env,
+                   v8::Local<v8::Object> req,
+                   AsyncWrap::ProviderType type,
+                   bool use_bigint);
+  ~FSReqBase() override;
+
+  inline void Init(const char* syscall,
+                   const char* data,
+                   size_t len,
+                   enum encoding encoding);
+  inline FSReqBuffer& Init(const char* syscall, size_t len,
+                           enum encoding encoding);
+
+  virtual void Reject(v8::Local<v8::Value> reject) = 0;
+  virtual void Resolve(v8::Local<v8::Value> value) = 0;
   virtual void ResolveStat(const uv_stat_t* stat) = 0;
-  virtual void SetReturnValue(const FunctionCallbackInfo<Value>& args) = 0;
+  virtual void SetReturnValue(
+      const v8::FunctionCallbackInfo<v8::Value>& args) = 0;
 
   const char* syscall() const { return syscall_; }
   const char* data() const { return has_data_ ? *buffer_ : nullptr; }
   enum encoding encoding() const { return encoding_; }
-
   bool use_bigint() const { return use_bigint_; }
 
+  FSContinuationData* continuation_data() const {
+    return continuation_data_.get();
+  }
+  void set_continuation_data(std::unique_ptr<FSContinuationData> data) {
+    continuation_data_ = std::move(data);
+  }
+
   static FSReqBase* from_req(uv_fs_t* req) {
     return static_cast<FSReqBase*>(ReqWrap::from_req(req));
   }
@@ -120,7 +77,10 @@ class FSReqBase : public ReqWrap<uv_fs_t> {
   FSReqBase(const FSReqBase&) = delete;
   FSReqBase& operator=(const FSReqBase&) = delete;
 
+  void MemoryInfo(MemoryTracker* tracker) const override;
+
  private:
+  std::unique_ptr<FSContinuationData> continuation_data_;
   enum encoding encoding_ = UTF8;
   bool has_data_ = false;
   const char* syscall_ = nullptr;
@@ -131,19 +91,16 @@ class FSReqBase : public ReqWrap<uv_fs_t> {
   FSReqBuffer buffer_;
 };
 
-class FSReqCallback : public FSReqBase {
+class FSReqCallback final : public FSReqBase {
  public:
-  FSReqCallback(Environment* env, Local<Object> req, bool use_bigint)
-      : FSReqBase(env, req, AsyncWrap::PROVIDER_FSREQCALLBACK, use_bigint) { }
+  inline FSReqCallback(Environment* env,
+                       v8::Local<v8::Object> req,
+                       bool use_bigint);
 
-  void Reject(Local<Value> reject) override;
-  void Resolve(Local<Value> value) override;
+  void Reject(v8::Local<v8::Value> reject) override;
+  void Resolve(v8::Local<v8::Value> value) override;
   void ResolveStat(const uv_stat_t* stat) override;
-  void SetReturnValue(const FunctionCallbackInfo<Value>& args) override;
-
-  void MemoryInfo(MemoryTracker* tracker) const override {
-    tracker->TrackField("continuation_data", continuation_data);
-  }
+  void SetReturnValue(const v8::FunctionCallbackInfo<v8::Value>& args) override;
 
   SET_MEMORY_INFO_NAME(FSReqCallback)
   SET_SELF_SIZE(FSReqCallback)
@@ -153,120 +110,27 @@ class FSReqCallback : public FSReqBase {
 };
 
 template <typename NativeT, typename V8T>
-constexpr void FillStatsArray(AliasedBufferBase<NativeT, V8T>* fields,
-                              const uv_stat_t* s,
-                              const size_t offset = 0) {
-#define SET_FIELD_WITH_STAT(stat_offset, stat)                               \
-  fields->SetValue(offset + static_cast<size_t>(FsStatsOffset::stat_offset), \
-                   static_cast<NativeT>(stat))
-
-#define SET_FIELD_WITH_TIME_STAT(stat_offset, stat)                          \
-  /* NOLINTNEXTLINE(runtime/int) */                                          \
-  SET_FIELD_WITH_STAT(stat_offset, static_cast<unsigned long>(stat))
-
-  SET_FIELD_WITH_STAT(kDev, s->st_dev);
-  SET_FIELD_WITH_STAT(kMode, s->st_mode);
-  SET_FIELD_WITH_STAT(kNlink, s->st_nlink);
-  SET_FIELD_WITH_STAT(kUid, s->st_uid);
-  SET_FIELD_WITH_STAT(kGid, s->st_gid);
-  SET_FIELD_WITH_STAT(kRdev, s->st_rdev);
-  SET_FIELD_WITH_STAT(kBlkSize, s->st_blksize);
-  SET_FIELD_WITH_STAT(kIno, s->st_ino);
-  SET_FIELD_WITH_STAT(kSize, s->st_size);
-  SET_FIELD_WITH_STAT(kBlocks, s->st_blocks);
-
-  SET_FIELD_WITH_TIME_STAT(kATimeSec, s->st_atim.tv_sec);
-  SET_FIELD_WITH_TIME_STAT(kATimeNsec, s->st_atim.tv_nsec);
-  SET_FIELD_WITH_TIME_STAT(kMTimeSec, s->st_mtim.tv_sec);
-  SET_FIELD_WITH_TIME_STAT(kMTimeNsec, s->st_mtim.tv_nsec);
-  SET_FIELD_WITH_TIME_STAT(kCTimeSec, s->st_ctim.tv_sec);
-  SET_FIELD_WITH_TIME_STAT(kCTimeNsec, s->st_ctim.tv_nsec);
-  SET_FIELD_WITH_TIME_STAT(kBirthTimeSec, s->st_birthtim.tv_sec);
-  SET_FIELD_WITH_TIME_STAT(kBirthTimeNsec, s->st_birthtim.tv_nsec);
-
-#undef SET_FIELD_WITH_TIME_STAT
-#undef SET_FIELD_WITH_STAT
-}
-
-inline Local<Value> FillGlobalStatsArray(Environment* env,
-                                         const bool use_bigint,
-                                         const uv_stat_t* s,
-                                         const bool second = false) {
-  const ptrdiff_t offset =
-      second ? static_cast<ptrdiff_t>(FsStatsOffset::kFsStatsFieldsNumber) : 0;
-  if (use_bigint) {
-    auto* const arr = env->fs_stats_field_bigint_array();
-    FillStatsArray(arr, s, offset);
-    return arr->GetJSArray();
-  } else {
-    auto* const arr = env->fs_stats_field_array();
-    FillStatsArray(arr, s, offset);
-    return arr->GetJSArray();
-  }
-}
+void FillStatsArray(AliasedBufferBase<NativeT, V8T>* fields,
+                    const uv_stat_t* s,
+                    const size_t offset = 0);
+
+inline v8::Local<v8::Value> FillGlobalStatsArray(Environment* env,
+                                                 const bool use_bigint,
+                                                 const uv_stat_t* s,
+                                                 const bool second = false);
 
 template <typename AliasedBufferT>
-class FSReqPromise : public FSReqBase {
+class FSReqPromise final : public FSReqBase {
  public:
-  static FSReqPromise* New(Environment* env, bool use_bigint) {
-    v8::Local<Object> obj;
-    if (!env->fsreqpromise_constructor_template()
-             ->NewInstance(env->context())
-             .ToLocal(&obj)) {
-      return nullptr;
-    }
-    v8::Local<v8::Promise::Resolver> resolver;
-    if (!v8::Promise::Resolver::New(env->context()).ToLocal(&resolver) ||
-        obj->Set(env->context(), env->promise_string(), resolver).IsNothing()) {
-      return nullptr;
-    }
-    return new FSReqPromise(env, obj, use_bigint);
-  }
-
-  ~FSReqPromise() override {
-    // Validate that the promise was explicitly resolved or rejected.
-    CHECK(finished_);
-  }
-
-  void Reject(Local<Value> reject) override {
-    finished_ = true;
-    HandleScope scope(env()->isolate());
-    InternalCallbackScope callback_scope(this);
-    Local<Value> value =
-        object()->Get(env()->context(),
-                      env()->promise_string()).ToLocalChecked();
-    Local<Promise::Resolver> resolver = value.As<Promise::Resolver>();
-    USE(resolver->Reject(env()->context(), reject).FromJust());
-  }
-
-  void Resolve(Local<Value> value) override {
-    finished_ = true;
-    HandleScope scope(env()->isolate());
-    InternalCallbackScope callback_scope(this);
-    Local<Value> val =
-        object()->Get(env()->context(),
-                      env()->promise_string()).ToLocalChecked();
-    Local<Promise::Resolver> resolver = val.As<Promise::Resolver>();
-    USE(resolver->Resolve(env()->context(), value).FromJust());
-  }
-
-  void ResolveStat(const uv_stat_t* stat) override {
-    FillStatsArray(&stats_field_array_, stat);
-    Resolve(stats_field_array_.GetJSArray());
-  }
-
-  void SetReturnValue(const FunctionCallbackInfo<Value>& args) override {
-    Local<Value> val =
-        object()->Get(env()->context(),
-                      env()->promise_string()).ToLocalChecked();
-    Local<Promise::Resolver> resolver = val.As<Promise::Resolver>();
-    args.GetReturnValue().Set(resolver->GetPromise());
-  }
+  static inline FSReqPromise* New(Environment* env, bool use_bigint);
+  inline ~FSReqPromise() override;
 
-  void MemoryInfo(MemoryTracker* tracker) const override {
-    tracker->TrackField("stats_field_array", stats_field_array_);
-    tracker->TrackField("continuation_data", continuation_data);
-  }
+  inline void Reject(v8::Local<v8::Value> reject) override;
+  inline void Resolve(v8::Local<v8::Value> value) override;
+  inline void ResolveStat(const uv_stat_t* stat) override;
+  inline void SetReturnValue(
+      const v8::FunctionCallbackInfo<v8::Value>& args) override;
+  inline void MemoryInfo(MemoryTracker* tracker) const override;
 
   SET_MEMORY_INFO_NAME(FSReqPromise)
   SET_SELF_SIZE(FSReqPromise)
@@ -277,17 +141,15 @@ class FSReqPromise : public FSReqBase {
   FSReqPromise& operator=(const FSReqPromise&&) = delete;
 
  private:
-  FSReqPromise(Environment* env, v8::Local<v8::Object> obj, bool use_bigint)
-      : FSReqBase(env, obj, AsyncWrap::PROVIDER_FSREQPROMISE, use_bigint),
-        stats_field_array_(
-            env->isolate(),
-            static_cast<size_t>(FsStatsOffset::kFsStatsFieldsNumber)) {}
+  inline FSReqPromise(Environment* env,
+                      v8::Local<v8::Object> obj,
+                      bool use_bigint);
 
   bool finished_ = false;
   AliasedBufferT stats_field_array_;
 };
 
-class FSReqAfterScope {
+class FSReqAfterScope final {
  public:
   FSReqAfterScope(FSReqBase* wrap, uv_fs_t* req);
   ~FSReqAfterScope();
@@ -304,17 +166,18 @@ class FSReqAfterScope {
  private:
   FSReqBase* wrap_ = nullptr;
   uv_fs_t* req_ = nullptr;
-  HandleScope handle_scope_;
-  Context::Scope context_scope_;
+  v8::HandleScope handle_scope_;
+  v8::Context::Scope context_scope_;
 };
 
 class FileHandle;
 
 // A request wrap specifically for uv_fs_read()s scheduled for reading
 // from a FileHandle.
-class FileHandleReadWrap : public ReqWrap<uv_fs_t> {
+class FileHandleReadWrap final : public ReqWrap<uv_fs_t> {
  public:
   FileHandleReadWrap(FileHandle* handle, v8::Local<v8::Object> obj);
+  ~FileHandleReadWrap() override;
 
   static inline FileHandleReadWrap* from_req(uv_fs_t* req) {
     return static_cast<FileHandleReadWrap*>(ReqWrap::from_req(req));
@@ -333,7 +196,7 @@ class FileHandleReadWrap : public ReqWrap<uv_fs_t> {
 
 // A wrapper for a file descriptor that will automatically close the fd when
 // the object is garbage collected
-class FileHandle : public AsyncWrap, public StreamBase {
+class FileHandle final : public AsyncWrap, public StreamBase {
  public:
   static FileHandle* New(Environment* env,
                          int fd,
@@ -346,10 +209,10 @@ class FileHandle : public AsyncWrap, public StreamBase {
 
   // Will asynchronously close the FD and return a Promise that will
   // be resolved once closing is complete.
-  static void Close(const FunctionCallbackInfo<Value>& args);
+  static void Close(const v8::FunctionCallbackInfo<v8::Value>& args);
 
   // Releases ownership of the FD.
-  static void ReleaseFD(const FunctionCallbackInfo<Value>& args);
+  static void ReleaseFD(const v8::FunctionCallbackInfo<v8::Value>& args);
 
   // StreamBase interface:
   int ReadStart() override;
@@ -366,13 +229,9 @@ class FileHandle : public AsyncWrap, public StreamBase {
   int DoWrite(WriteWrap* w,
               uv_buf_t* bufs,
               size_t count,
-              uv_stream_t* send_handle) override {
-    return UV_ENOSYS;  // Not implemented (yet).
-  }
+              uv_stream_t* send_handle) override;
 
-  void MemoryInfo(MemoryTracker* tracker) const override {
-    tracker->TrackField("current_read", current_read_);
-  }
+  void MemoryInfo(MemoryTracker* tracker) const override;
 
   SET_MEMORY_INFO_NAME(FileHandle)
   SET_SELF_SIZE(FileHandle)
@@ -389,36 +248,24 @@ class FileHandle : public AsyncWrap, public StreamBase {
   void Close();
   void AfterClose();
 
-  class CloseReq : public ReqWrap<uv_fs_t> {
+  class CloseReq final : public ReqWrap<uv_fs_t> {
    public:
     CloseReq(Environment* env,
-             Local<Object> obj,
-             Local<Promise> promise,
-             Local<Value> ref)
-        : ReqWrap(env, obj, AsyncWrap::PROVIDER_FILEHANDLECLOSEREQ) {
-      promise_.Reset(env->isolate(), promise);
-      ref_.Reset(env->isolate(), ref);
-    }
-
-    ~CloseReq() override {
-      uv_fs_req_cleanup(req());
-      promise_.Reset();
-      ref_.Reset();
-    }
+             v8::Local<v8::Object> obj,
+             v8::Local<v8::Promise> promise,
+             v8::Local<v8::Value> ref);
+    ~CloseReq() override;
 
     FileHandle* file_handle();
 
-    void MemoryInfo(MemoryTracker* tracker) const override {
-      tracker->TrackField("promise", promise_);
-      tracker->TrackField("ref", ref_);
-    }
+    void MemoryInfo(MemoryTracker* tracker) const override;
 
     SET_MEMORY_INFO_NAME(CloseReq)
     SET_SELF_SIZE(CloseReq)
 
     void Resolve();
 
-    void Reject(Local<Value> reason);
+    void Reject(v8::Local<v8::Value> reason);
 
     static CloseReq* from_req(uv_fs_t* req) {
       return static_cast<CloseReq*>(ReqWrap::from_req(req));
@@ -430,12 +277,12 @@ class FileHandle : public AsyncWrap, public StreamBase {
     CloseReq& operator=(const CloseReq&&) = delete;
 
    private:
-    v8::Global<Promise> promise_{};
-    v8::Global<Value> ref_{};
+    v8::Global<v8::Promise> promise_{};
+    v8::Global<v8::Value> ref_{};
   };
 
   // Asynchronous close
-  inline MaybeLocal<Promise> ClosePromise();
+  v8::MaybeLocal<v8::Promise> ClosePromise();
 
   int fd_;
   bool closing_ = false;
@@ -467,53 +314,23 @@ class FSReqWrapSync {
 // that nullptr indicates a synchronous call, rather than a failure.
 // Failure conditions should be disambiguated and handled appropriately.
 inline FSReqBase* GetReqWrap(Environment* env, v8::Local<v8::Value> value,
-                             bool use_bigint = false) {
-  if (value->IsObject()) {
-    return Unwrap<FSReqBase>(value.As<Object>());
-  } else if (value->StrictEquals(env->fs_use_promises_symbol())) {
-    if (use_bigint) {
-      return FSReqPromise<AliasedBigUint64Array>::New(env, use_bigint);
-    } else {
-      return FSReqPromise<AliasedFloat64Array>::New(env, use_bigint);
-    }
-  }
-  return nullptr;
-}
+                             bool use_bigint = false);
 
 // Returns nullptr if the operation fails from the start.
 template <typename Func, typename... Args>
 inline FSReqBase* AsyncDestCall(Environment* env, FSReqBase* req_wrap,
-                                const v8::FunctionCallbackInfo<Value>& args,
+                                const v8::FunctionCallbackInfo<v8::Value>& args,
                                 const char* syscall, const char* dest,
                                 size_t len, enum encoding enc, uv_fs_cb after,
-                                Func fn, Args... fn_args) {
-  CHECK_NOT_NULL(req_wrap);
-  req_wrap->Init(syscall, dest, len, enc);
-  int err = req_wrap->Dispatch(fn, fn_args..., after);
-  if (err < 0) {
-    uv_fs_t* uv_req = req_wrap->req();
-    uv_req->result = err;
-    uv_req->path = nullptr;
-    after(uv_req);  // after may delete req_wrap if there is an error
-    req_wrap = nullptr;
-  } else {
-    req_wrap->SetReturnValue(args);
-  }
-
-  return req_wrap;
-}
+                                Func fn, Args... fn_args);
 
 // Returns nullptr if the operation fails from the start.
 template <typename Func, typename... Args>
 inline FSReqBase* AsyncCall(Environment* env,
                             FSReqBase* req_wrap,
-                            const v8::FunctionCallbackInfo<Value>& args,
+                            const v8::FunctionCallbackInfo<v8::Value>& args,
                             const char* syscall, enum encoding enc,
-                            uv_fs_cb after, Func fn, Args... fn_args) {
-  return AsyncDestCall(env, req_wrap, args,
-                       syscall, nullptr, 0, enc,
-                       after, fn, fn_args...);
-}
+                            uv_fs_cb after, Func fn, Args... fn_args);
 
 // Template counterpart of SYNC_CALL, except that it only puts
 // the error number and the syscall in the context instead of
@@ -522,22 +339,7 @@ inline FSReqBase* AsyncCall(Environment* env,
 template <typename Func, typename... Args>
 inline int SyncCall(Environment* env, v8::Local<v8::Value> ctx,
                     FSReqWrapSync* req_wrap, const char* syscall,
-                    Func fn, Args... args) {
-  env->PrintSyncTrace();
-  int err = fn(env->event_loop(), &(req_wrap->req), args..., nullptr);
-  if (err < 0) {
-    v8::Local<Context> context = env->context();
-    v8::Local<Object> ctx_obj = ctx.As<v8::Object>();
-    v8::Isolate* isolate = env->isolate();
-    ctx_obj->Set(context,
-                 env->errno_string(),
-                 v8::Integer::New(isolate, err)).Check();
-    ctx_obj->Set(context,
-                 env->syscall_string(),
-                 OneByteString(isolate, syscall)).Check();
-  }
-  return err;
-}
+                    Func fn, Args... args);
 
 }  // namespace fs
 
diff --git a/src/node_stat_watcher.cc b/src/node_stat_watcher.cc
index ae30825cbbdbd2..a3f4ef8f505d39 100644
--- a/src/node_stat_watcher.cc
+++ b/src/node_stat_watcher.cc
@@ -23,7 +23,7 @@
 #include "node_stat_watcher.h"
 #include "async_wrap-inl.h"
 #include "env.h"
-#include "node_file.h"
+#include "node_file-inl.h"
 #include "util-inl.h"
 
 #include <cstring>
diff --git a/src/req_wrap-inl.h b/src/req_wrap-inl.h
index cf89fb58a7f6fc..bfd083cf2389ce 100644
--- a/src/req_wrap-inl.h
+++ b/src/req_wrap-inl.h
@@ -119,7 +119,7 @@ struct MakeLibuvRequestCallback<ReqT, void(*)(ReqT*, Args...)> {
   using F = void(*)(ReqT* req, Args... args);
 
   static void Wrapper(ReqT* req, Args... args) {
-    ReqWrap<ReqT>* req_wrap = ContainerOf(&ReqWrap<ReqT>::req_, req);
+    ReqWrap<ReqT>* req_wrap = ReqWrap<ReqT>::from_req(req);
     req_wrap->env()->DecreaseWaitingRequestCounter();
     F original_callback = reinterpret_cast<F>(req_wrap->original_callback_);
     original_callback(req, args...);
diff --git a/src/req_wrap.h b/src/req_wrap.h
index 36eeb1cbc24005..5d7fcb42d01148 100644
--- a/src/req_wrap.h
+++ b/src/req_wrap.h
@@ -50,9 +50,10 @@ class ReqWrap : public AsyncWrap, public ReqWrapBase {
 
  private:
   friend int GenDebugSymbols();
-  template <typename ReqT, typename U>
-  friend struct MakeLibuvRequestCallback;
 
+  // Adding `friend struct MakeLibuvRequestCallback` is not enough anymore
+  // for some reason. Consider this private.
+ public:
   typedef void (*callback_t)();
   callback_t original_callback_ = nullptr;