问题描述
问题
我想出的解决方案通常是如何解决这个问题的?或者是否有更常见、更直接、“更好”的解决方法?这些替代方案是什么?它们的好处是什么?
详情
我正在用 Node.js 编写一个脚本,我希望该脚本fail fast。因此,我编写了以下函数以在多个地方用作回调:
function throwIfError(err) {
if(err) throw err;
}
但是,当我使用它时,比如 fs.writeFile
,我没有得到有用的堆栈跟踪:
function throwIfError(err) {
if(err) throw err;
}
require('fs').writeFile(
'some_non_existent_directory/some_file','',throwIfError,);
$ node script.js
/path/to/script.js:2
if(err) throw err;
^
[Error: ENOENT: no such file or directory,open 'some_non_existent_directory/some_file'] {
errno: -2,code: 'ENOENT',syscall: 'open',path: 'some_non_existent_directory/some_file'
}
$
这里没有任何内容告诉我错误的来源是第 5 行对 writeFile
的调用。脚本可能有多个 writeFile
调用,我不知道错误源自何处。
使用 console.trace()
也无济于事:
function throwIfError(err) {
console.trace();
}
require('fs').writeFile(
'some_non_existent_directory/some_file',);
$ node script.js
Trace
at throwIfError (/path/to/script.js:2:11)
at fs.js:1448:7
at FSReqCallback.oncomplete (fs.js:171:23)
$
不是在一个地方定义回调函数并在多个地方使用它,而是在每个函数调用中定义内联回调来修复它:
require('fs').writeFile(
'some_non_existent_directory/some_file',err => {if(err) throw err},);
$ node script.js
/path/to/script.js:4
err => {if(err) throw err},^
[Error: ENOENT: no such file or directory,path: 'some_non_existent_directory/some_file'
}
$
由于回调现在是内联的,所以抛出错误的地方与进行“有问题”函数调用的地方是同一个地方。由于运行时会告诉我抛出错误的位置,这意味着我隐式地知道错误发生在哪个函数调用中。
然而,问题在于代码重复。这样做意味着在许多地方一遍又一遍地定义相同的函数(回调)。如果我想更改回调,所有都需要更改,因为所有实际上都是彼此重复的。另一个问题是垃圾收集器的负载增加。在前一种方式中,只有一个回调并且只是对它的引用。但是,这样一来,回调的次数和使用的次数一样多。
第一个问题(重复的回调定义以及当你需要改变一个时需要改变每一个)可以用下面的“辅助回调”模式来解决:
require('fs').writeFile(
'some_non_existent_directory/some_file',err => throwIfError(err),);
function throwIfError(err) {
if(err) {
console.log('Callback is located at:');
console.trace();
throw err;
}
}
$ node script.js
Callback is located at:
Trace
at throwIfError (/path/to/the/script.js:10:13)
at /path/to/the/script.js:4:10
at fs.js:1448:7
at FSReqCallback.oncomplete (fs.js:171:23)
/path/to/the/script.js:11
throw err;
^
[Error: ENOENT: no such file or directory,path: 'some_non_existent_directory/some_file'
}
$
在这里,console.trace
输出中的第二行让我了解回调的使用位置。这样,我就知道是哪个函数调用导致了这个错误。
这种模式的一个优点是,如果(真实的)回调需要改变,它可以简单地在一个地方改变,也就是它被定义的地方。但是,仍然存在垃圾收集器负载增加的问题,因为我们在每个需要使用 throwIfError
回调的地方都定义了匿名“助手”回调。
这个想法的一个更高级的实现如下:
require('fs').writeFile(
'some_non_existent_directory/some_file',);
// NOTE: This works only if it is used with an inline helper callback like `err
// => throwIfError(err)`,which is a function _expression_ that's passed to a
// function invocation which expects a callback. If the following function is
// used by itself,like `throwIfError`,it won't work!
function throwIfError(err) {
if (!err) return;
const currentStack = getCurrentStack(
1,'Info: First line below shows where this callback was used'
);
err.stack = currentStack + '\n' + err.stack;
// Following is so that the stack will be formatted properly when it is
// printed. The `formatError` function in 'lib/internal/util/inspect.js'
// ignores everything in the `stack` property until the end of the value of
// the `message` property in the `stack` property. That is,the `err.message`
// is present in `err.stack` by default,because when creating an error with
// an argument,the argument becomes the `message` property,and the value of
// the `stack` property is generated using the:
//
// - Name of the error type
// - Error message
// - Stack trace at the time that the error is created
//
// Since the `message` property is present in the `stack` property by default,// and since the `formatError` function in 'lib/internal/util/inspect.js'
// ignores everything until the end of the value of the `message` in the
// `stack` property,we remove the `message` property so that the part of the
// stack before the value of the `message` property will not be ignored (that
// is,it will be formatted properly for printing).
//
// The lines in the `formatError` function in 'lib/internal/util/inspect.js'
// that cause this are as follows:
//
// // Ignore the error message if it's contained in the stack.
// let pos = (err.message && stack.indexOf(err.message)) || -1;
// if (pos !== -1)
// pos += err.message.length;
delete err.message;
throw err;
}
function getCurrentStack(
numAdditionalFramesToRemove = 0,message = 'Info: Current stack is'
) {
const stackContainer = {};
Error.captureStackTrace(stackContainer);
stackContainer.stack = stackContainer.stack.split('\n');
// The magic number 2 below means "Always remove two stack frames". The reason
// is:
//
// - The first line of the output of `Error.captureStackTrace` includes a line
// that just says "Error". This is not useful and we are replacing it with
// our custom message using the `message` parameter already.
// - The second line is the frame for the invocation of this method. It is
// Meta information to the user and hence,we are removing it.
stackContainer.stack.splice(0,2 + numAdditionalFramesToRemove,message);
stackContainer.stack = stackContainer.stack.join('\n');
return stackContainer.stack;
}
$ node script.js
/path/to/the/script.js:47
throw err;
^
Info: First line below shows where this callback was used
at /path/to/the/script.js:4:10
at fs.js:1448:7
at FSReqCallback.oncomplete (fs.js:171:23)
Error: ENOENT: no such file or directory,open 'some_non_existent_directory/some_file' {
errno: -2,path: 'some_non_existent_directory/some_file'
}
$
虽然这并不能解决由于匿名“helper”回调函数导致垃圾收集器负载增加的问题,但这是我能想到的最好的方法。
解决方法
暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!
如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。
小编邮箱:dio#foxmail.com (将#修改为@)