如何优雅地为数组中列出的即将 executed/invoked 的函数和方法提供 try-catch 功能?
How does one elegantly provide try-catch functionality to functions and methods which are listed within an array and are about to be executed/invoked?
我有这个数组:
const arr = [
{ 'Some text': this.f('a') },
{ 'Some other text': this.f('b') }
]
我为此编写了实验代码。
arr.forEach((entry) => {
console.log(Object.entries(entry));
})
结果它执行了我的功能:
[[ 'Some text': 'result_f1' ]]
[[ 'Some other text': 'result_f2' ]]
我的函数自动执行了。
但是我认为这不是实现该目标的最安全方法。我想更明确地分别执行每个函数以及尝试捕获每个函数。
我想这样做的原因是将每个函数包装到一个单独的 try-catch
块中,因为在数组中尝试捕获会破坏代码的可读性
有什么实现方法吗?
这些函数不是“自动”执行的,而是因为您显式调用它们而执行的:
const arr = [
{ 'Some text': this.f('a') },
// ^^^^^^^^^^^−−−−−−−−−−−−−− here
{ 'Some other text': this.f('b') }
// ^^^^^^^^^^^−−−−−−−− and here
]
上面的结果是一个包含两个对象的数组,其中第一个对象有一个名为 Some text
的 属性,其值为 result 调用了 this.f('a')
,第二个对象有一个名为 Some other text
的 属性,其值为调用了 this.f('b')
的 结果 。
如果您想将这些调用延迟到稍后的某个时间,您需要将它们包装在函数中:
const arr = [
{ 'Some text': () => this.f('a') },
// ^^^^^^^^^^^^^^^^^−−−−−−−−−−−−−− wrapper function
{ 'Some other text': () => this.f('b') }
// ^^^^^^^^^^^^^^^^^−−−−−−−− wrapper function
]
以后,你会这样称呼他们:
arr[0]["Some text"]();
arr[0]["Some other tex"]();
或类似。
如果你想稍后在 try
/catch
块中调用它们,你可以这样做:
for (const obj of arr) {
for (const fn of Object.values(obj)) {
try {
fn();
} catch (e) {
// ...
}
}
}
...或等同于 forEach
:
arr.forEach(obj => {
Object.values(obj).forEach(fn => {
try {
fn();
} catch (e) {
// ...
}
});
});
在您说的另一条评论中:
how would you replace the function with function result?
我怀疑你想要这样的东西:
const arr = [
{ 'Some text': () => this.f('a') },
{ 'Some other text': () => this.f('b') }
].map(obj => {
return Object.fromEntries(Object.entries(obj).map(([key, value]) => {
try {
value = value();
} catch (e) {
// ...presumably do something with the fact it failed...
}
return [key, value];
}));
});
上面的结果是 arr
具有与初始对象字面量相同的键的对象,但值是调用函数的结果(或您写入 [=24= 的任何内容) ] 在 catch
块中)。
参见:
定义数组 arr
时已调用函数 this.f
。这就是圆括号的作用。要推迟调用,您可以将参数“绑定”到函数,而无需实际调用函数 this.f.bin(this, 'a')
而不是 this.f('a')
。这会创建一种可以在稍后阶段调用的闭包。
在 forEach
中,您可以显式调用函数:
arr.forEach((entry) => {
Object.entries(entry).forEach(([key, fun]) => {
console.log(key, fun())
})
})
话虽如此,我不确定为什么要这样做。
+++ 迟到的答案,但我们开始了...+++
对于像 OP 描述的任务或类似的任务,例如... ... one could rely on two method modifier abstractions like (以后使用)and/or afterFinally
两者都专门针对 try-catch 基于 functions/methods 的异常处理,它们本身没有实现故障安全。
上面提到的两个抽象(将)提供包装 function/method 的功能,其方式不仅提供 try-catch 但也考虑了异常处理。
像 OP 这样的用例可以像下面的示例代码一样直接实现...
const testDummy = {
x: 'x',
y: 'y',
foo: function (a, b) { return [this.x, this.y, a, b].join(' :: '); },
bar: function (a, b) { throw new Error(`exception raised with arguments a: ${ a } and b: ${ b }`) } ,
};
const testConfig = {
'non failing method invocation': testDummy.foo.bind(testDummy, 'aa', 'bb'),
'method invocation with raised exception': testDummy.bar.bind(testDummy, 'bazz', 'bizz'),
};
function handleException({ name, message, stack }/*, argsList */) {
console.log(
'handleException :: error ...', { name, message, stack }
);
return 'custom/sanitized return value for raised exception';
}
function createAndExecuteFailSafeTestAndReturnResult([description, executee]) {
const test = executee.afterThrowing(handleException/*, target */);
const result = test();
return [[description, 'result ...'].join(' :: '), result];
}
console.log({
testConfig,
testResult: Object.fromEntries(
Object
.entries(testConfig)
.map(createAndExecuteFailSafeTestAndReturnResult)
)
});
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
(function (Function) {
function isFunction(value) {
return (
typeof value === 'function' &&
typeof value.call === 'function' &&
typeof value.apply === 'function'
);
}
function getSanitizedTarget(value) {
return value ?? null;
}
function afterThrowing/*Modifier*/(handler, target) {
target = getSanitizedTarget(target);
const proceed = this;
return (
isFunction(handler) &&
isFunction(proceed) &&
function afterThrowingType(...argumentArray) {
const context = getSanitizedTarget(this) ?? target;
let result;
try {
// try the invocation of the original function.
result = proceed.apply(context, argumentArray);
} catch (exception) {
result = handler.call(context, exception, argumentArray);
}
return result;
}
) || proceed;
}
// afterThrowing.toString = () => 'afterThrowing() { [native code] }';
Object.defineProperty(Function.prototype, 'afterThrowing', {
configurable: true,
writable: true,
value: afterThrowing/*Modifier*/
});
}(Function));
</script>
我有这个数组:
const arr = [
{ 'Some text': this.f('a') },
{ 'Some other text': this.f('b') }
]
我为此编写了实验代码。
arr.forEach((entry) => {
console.log(Object.entries(entry));
})
结果它执行了我的功能:
[[ 'Some text': 'result_f1' ]]
[[ 'Some other text': 'result_f2' ]]
我的函数自动执行了。
但是我认为这不是实现该目标的最安全方法。我想更明确地分别执行每个函数以及尝试捕获每个函数。
我想这样做的原因是将每个函数包装到一个单独的 try-catch
块中,因为在数组中尝试捕获会破坏代码的可读性
有什么实现方法吗?
这些函数不是“自动”执行的,而是因为您显式调用它们而执行的:
const arr = [
{ 'Some text': this.f('a') },
// ^^^^^^^^^^^−−−−−−−−−−−−−− here
{ 'Some other text': this.f('b') }
// ^^^^^^^^^^^−−−−−−−− and here
]
上面的结果是一个包含两个对象的数组,其中第一个对象有一个名为 Some text
的 属性,其值为 result 调用了 this.f('a')
,第二个对象有一个名为 Some other text
的 属性,其值为调用了 this.f('b')
的 结果 。
如果您想将这些调用延迟到稍后的某个时间,您需要将它们包装在函数中:
const arr = [
{ 'Some text': () => this.f('a') },
// ^^^^^^^^^^^^^^^^^−−−−−−−−−−−−−− wrapper function
{ 'Some other text': () => this.f('b') }
// ^^^^^^^^^^^^^^^^^−−−−−−−− wrapper function
]
以后,你会这样称呼他们:
arr[0]["Some text"]();
arr[0]["Some other tex"]();
或类似。
如果你想稍后在 try
/catch
块中调用它们,你可以这样做:
for (const obj of arr) {
for (const fn of Object.values(obj)) {
try {
fn();
} catch (e) {
// ...
}
}
}
...或等同于 forEach
:
arr.forEach(obj => {
Object.values(obj).forEach(fn => {
try {
fn();
} catch (e) {
// ...
}
});
});
在您说的另一条评论中:
how would you replace the function with function result?
我怀疑你想要这样的东西:
const arr = [
{ 'Some text': () => this.f('a') },
{ 'Some other text': () => this.f('b') }
].map(obj => {
return Object.fromEntries(Object.entries(obj).map(([key, value]) => {
try {
value = value();
} catch (e) {
// ...presumably do something with the fact it failed...
}
return [key, value];
}));
});
上面的结果是 arr
具有与初始对象字面量相同的键的对象,但值是调用函数的结果(或您写入 [=24= 的任何内容) ] 在 catch
块中)。
参见:
定义数组 arr
时已调用函数 this.f
。这就是圆括号的作用。要推迟调用,您可以将参数“绑定”到函数,而无需实际调用函数 this.f.bin(this, 'a')
而不是 this.f('a')
。这会创建一种可以在稍后阶段调用的闭包。
在 forEach
中,您可以显式调用函数:
arr.forEach((entry) => {
Object.entries(entry).forEach(([key, fun]) => {
console.log(key, fun())
})
})
话虽如此,我不确定为什么要这样做。
+++ 迟到的答案,但我们开始了...+++
对于像 OP 描述的任务或类似的任务,例如... afterFinally
两者都专门针对 try-catch 基于 functions/methods 的异常处理,它们本身没有实现故障安全。
上面提到的两个抽象(将)提供包装 function/method 的功能,其方式不仅提供 try-catch 但也考虑了异常处理。
像 OP 这样的用例可以像下面的示例代码一样直接实现...
const testDummy = {
x: 'x',
y: 'y',
foo: function (a, b) { return [this.x, this.y, a, b].join(' :: '); },
bar: function (a, b) { throw new Error(`exception raised with arguments a: ${ a } and b: ${ b }`) } ,
};
const testConfig = {
'non failing method invocation': testDummy.foo.bind(testDummy, 'aa', 'bb'),
'method invocation with raised exception': testDummy.bar.bind(testDummy, 'bazz', 'bizz'),
};
function handleException({ name, message, stack }/*, argsList */) {
console.log(
'handleException :: error ...', { name, message, stack }
);
return 'custom/sanitized return value for raised exception';
}
function createAndExecuteFailSafeTestAndReturnResult([description, executee]) {
const test = executee.afterThrowing(handleException/*, target */);
const result = test();
return [[description, 'result ...'].join(' :: '), result];
}
console.log({
testConfig,
testResult: Object.fromEntries(
Object
.entries(testConfig)
.map(createAndExecuteFailSafeTestAndReturnResult)
)
});
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
(function (Function) {
function isFunction(value) {
return (
typeof value === 'function' &&
typeof value.call === 'function' &&
typeof value.apply === 'function'
);
}
function getSanitizedTarget(value) {
return value ?? null;
}
function afterThrowing/*Modifier*/(handler, target) {
target = getSanitizedTarget(target);
const proceed = this;
return (
isFunction(handler) &&
isFunction(proceed) &&
function afterThrowingType(...argumentArray) {
const context = getSanitizedTarget(this) ?? target;
let result;
try {
// try the invocation of the original function.
result = proceed.apply(context, argumentArray);
} catch (exception) {
result = handler.call(context, exception, argumentArray);
}
return result;
}
) || proceed;
}
// afterThrowing.toString = () => 'afterThrowing() { [native code] }';
Object.defineProperty(Function.prototype, 'afterThrowing', {
configurable: true,
writable: true,
value: afterThrowing/*Modifier*/
});
}(Function));
</script>