-3

I have this array:

const arr = [
  { 'Some text': this.f('a') },
  { 'Some other text': this.f('b') }
]

And I wrote experimental code in order to achieve that.

arr.forEach((entry) => {
  console.log(Object.entries(entry));
})

As a result it executed my functions:

[[ 'Some text': 'result_f1' ]]
[[ 'Some other text': 'result_f2' ]]

My functions got executed automatically.

However I do not think that this is the most safest way to achieve that. I want to be more explicit executing each function separately as well as try-catching each function.

The reason I want to do that is to wrap each function into a separate try-catch block as try-catching within an array is breaking the code's readability

Any ideas how to achieve that?

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
Sergino
  • 10,128
  • 30
  • 98
  • 159
  • 1
    `this.f('a')` runs the function immediately and stores the result as property `some text`. What were you expecting to happen? – phuzi Oct 05 '21 at 10:03
  • @phuzi I want to execute functions and return result without using console.log – Sergino Oct 05 '21 at 10:05
  • 2
    Console.log is not running the functions, they are run at the point the array is created before the `arr.forEach` as well. – phuzi Oct 05 '21 at 10:06
  • @Sergino ... Yet another, late though, answer/approach was added. – Peter Seliger May 04 '23 at 00:14

3 Answers3

6

The functions are not being executed "automatically," they're being executed because you're explicitly calling them:

const arr = [
    { 'Some text': this.f('a') },
    //             ^^^^^^^^^^^−−−−−−−−−−−−−− here
    { 'Some other text': this.f('b') }
    //                   ^^^^^^^^^^^−−−−−−−− and here
]

The result of the above is an array with two objects in it, where the first object has a property called Some text whose value is the result of having called this.f('a') and the second object has a property called Some other text whose value is the result of having called this.f('b').

If you wanted to delay those calls until some later time, you'd need to wrap them in functions:

const arr = [
    { 'Some text': () => this.f('a') },
    //             ^^^^^^^^^^^^^^^^^−−−−−−−−−−−−−− wrapper function
    { 'Some other text': () => this.f('b') }
    //                   ^^^^^^^^^^^^^^^^^−−−−−−−− wrapper function
]

Later, you'd call them like this:

arr[0]["Some text"]();
arr[0]["Some other tex"]();

or similar.

If you want to call them later wrapped in try/catch blocks, you could do it like this:

for (const obj of arr) {
    for (const fn of Object.values(obj)) {
        try {
            fn();
        } catch (e) {
            // ...
        }
    }
}

...or the equivalent with forEach:

arr.forEach(obj => {
    Object.values(obj).forEach(fn => {
        try {
            fn();
        } catch (e) {
            // ...
        }
    });
});

In another comment you've said:

how would you replace the function with function result?

I suspect you want something like this:

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];
    }));
});

The result of the above is that arr has objects with the same keys as the initial object literal, but with the values being the result of calling the functions (or whatever you write to value in the catch block).

See:

Spectric
  • 30,714
  • 6
  • 20
  • 43
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
1

The function this.f was already called when you defined the array arr. This is what the round braces do. To postpone the invocation you could "bind" the parameters to the function without actually calling the function this.f.bin(this, 'a') instead of this.f('a'). This creates a sort of closure that can be called at a later stage.

In the forEach you could then explicitly call the functions:

arr.forEach((entry) => {
  Object.entries(entry).forEach(([key, fun]) => {
    console.log(key, fun())
  })
})

That being said, I am unsure why one would want to do this.

Moritz
  • 432
  • 5
  • 15
1

+++ Late answer, but here we go ...+++

For tasks like the one described by the OP or similar ones like ... "How to use common try-catch for processing every given function in Javascript?" ... one could rely on two method modifier abstractions like afterThrowing (utilized hereafter) and/or afterFinally which both specifically target try-catch based exception-handling for functions/methods that are not implemented fail-safe themself.

The above mentioned two abstractions (would) provide functionality which wraps a function/method in a way that not only provides the try-catch but also takes the exception handling into account.

Use cases like the one of the OP then can be implemented as straightforward as demonstrated with the next following example code ...

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>
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37