首页 > 解决方案 > fs.promises.readFile ENOENT 错误中没有堆栈

问题描述

const fs = require('fs');

async function read() {
  return fs.promises.readFile('non-exist');
}

read()
  .then(() => console.log('done'))
  .catch(err => {
    console.log(err);
  })

给出:

➜  d2e2027b node app.js
[Error: ENOENT: no such file or directory, open 'non-exist'] {
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: 'non-exist'
}
➜  d2e2027b

堆栈丢失。如果我改用fs.readFileSync它,它会按预期显示堆栈。

➜  d2e2027b node app.js
Error: ENOENT: no such file or directory, open 'non-exist'
    at Object.openSync (node:fs:582:3)
    at Object.readFileSync (node:fs:450:35)
    at read (/private/tmp/d2e2027b/app.js:4:13)
    at Object.<anonymous> (/private/tmp/d2e2027b/app.js:8:1)

作为一个超级丑陋的解决方法,我可以尝试/捕获并在 ENOENT 的情况下抛出一个新错误,但我确信那里有更好的解决方案。

read()
  .then(() => console.log('done'))
  .catch(err => {
     if (err.code === 'ENOENT') throw new Error(`ENOENT: no such file or directory, open '${err.path}'`);
    console.log(err);
  })

(我尝试了节点 v12、v14、v16 - 相同)

标签: node.jspromisefs

解决方案


Nodejs 有几个模块会抛出无用stack属性的错误;在我看来,这是一个错误,但它从 nodejs 开始就已经存在,并且由于担心向后兼容性可能无法更改(编辑:我收回这个;该stack属性是非标准的,开发人员应该知道不要依赖于它的结构;nodejs 真的应该做出改变以抛出更有意义的错误)。

我已经包装了我在 nodejs 中使用的所有这些函数,修改它们以抛出好的错误。可以使用此函数创建此类包装器:

let formatErr = (err, stack) => {
  // The new stack is the original Error's message, followed by
  // all the stacktrace lines (Omit the first line in the stack,
  // which will simply be "Error")
  err.stack = [ err.message, ...stack.split('\n').slice(1) ].join('\n');
  return err;
};
let traceableErrs = fnWithUntraceableErrs => {
    
  return function(...args) {
    
    let stack = (new Error('')).stack;
    try {
      
      let result = fnWithUntraceableErrs(...args);
      
      // Handle Promises that resolve to bad Errors
      let isCatchable = true
        && result != null       // Intentional loose comparison
        && result.catch != null // Intentional loose comparison
        && (result.catch instanceof Function);
      return isCatchable
        ? result.catch(err => { throw formatErr(err, stack); })
        : result;
      
    } catch(err) {
      
      // Handle synchronously thrown bad Errors
      throw formatErr(err, stack);
      
    }
    
  };
  
}

这个包装器处理简单的函数、返回承诺的函数和异步函数。基本前提是在调用包装函数时初步生成堆栈;此堆栈将具有导致调用包装函数的调用者链。现在,如果抛出错误(同步或异步),我们会捕获错误,将其stack属性设置为有用的值,然后再次抛出它;如果您愿意,可以“捕获并释放”。

这是我使用这种方法在终端中看到的内容:

> let readFile = traceableErrs(require('fs').promises.readFile);
> (async () => await readFile('C:/nonexistent.txt'))().catch(console.log);
Promise { <pending> }
> ENOENT: no such file or directory, open 'C:\nonexistent.txt'
    at repl:5:18
    at repl:1:20
    at repl:1:48
    at Script.runInThisContext (vm.js:120:20)
    at REPLServer.defaultEval (repl.js:433:29)
    at bound (domain.js:426:14)
    at REPLServer.runBound [as eval] (domain.js:439:12)
    at REPLServer.onLine (repl.js:760:10)
    at REPLServer.emit (events.js:327:22)
    at REPLServer.EventEmitter.emit (domain.js:482:12) {
  errno: -4058,
  code: 'ENOENT',
  syscall: 'open',
  path: 'C:\\nonexistent.txt'
}

如果你想修改整个fs.promises套件以抛出好的错误,你可以这样做:

let fs = { ...require('fs').promises };
for (let k in fs) fs[k] = traceableErrs(fs[k]);

(async () => {
  // Now all `fs` functions throw stackful errors
  await fs.readFile(...);
  await fs.writeFile(...);
})();

推荐阅读