如何以允许在调用此功能之前和返回此功能后进行额外记录的方式包装 /装饰 /修改功能?

发布于 2025-01-30 13:42:13 字数 708 浏览 1 评论 0 原文

给定以下代码...

function logger = ??

function doSomething() {
  logger("doSomething")
  console.log("Middle of doing something")
}

是否可以定义 logger 使以下输出产生以下输出?

"started function doSomething"
"Middle of doing something"
"finished function doSomething"

我已经考虑了以下内容:

  • 封装 dosomething 在打印第一行的另一个函数中,调用 dosomething 在传递参数时,然后打印最后一行。

  • 做前面提到的同样的事情,但使用装饰剂更漂亮。

  • dosomething的末尾发送事件以通知Logger,并使用异步代码来执行此操作(这是在黑暗中拍摄的,不确定) >

在此期间暗示了我采访可以通过“与堆和呼叫堆栈有关的事物”而进行的采访。

是否有一个解决方案不涉及 dosomething 告诉 logger 完成后?

Given the following code...

function logger = ??

function doSomething() {
  logger("doSomething")
  console.log("Middle of doing something")
}

Is it possible to define logger such that the following output is produced?

"started function doSomething"
"Middle of doing something"
"finished function doSomething"

I've considered the following:

  • Encapsulating doSomething in another function that prints first line, calls doSomething while passing the arguments, then prints last line.

  • Doing the same thing previously mentioned but with a decorator so it's prettier.

  • Sending an event at the end of doSomething to notify logger it finished and using async code to do that (this one was kind of a shot in the dark, not sure about it)

It was hinted to me during an interview that this is possible via "something to do with heap and the call stack".

Is there a solution that does not involve doSomething telling logger when it has finished?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

你如我软肋 2025-02-06 13:42:14

我已经考虑过...封装...带装饰器...

这就是必须走的路。

dosomething的末尾发送事件以通知Logger,并使用异步代码...

使用异步代码仅在您已经具有 dosomething 的异步API时才相关。 。

是否有一个不涉及 dosomething 告诉logger何时完成的解决方案?

那是装饰者的基本思想。

例子:

let logger = console.log;

// Decorator
function logged(task) {
    return function(...args) {
        logger("starting " + task.name);
        const result = task(...args);
        logger("ending " + task.name);
        return result;
    };
}

// Your function -- unaware of logger
function doSomething(arg) {
  console.log("Middle of doing something with argument " + arg);
  return arg.toUpperCase();
}

// Decorating...
doSomething = logged(doSomething);

// Run...
let result = doSomething("test");
console.log(result);

更多功能...

您可以添加许多扩展/功能。例如:

  • 包括一个通用 decorator 功能,该功能将目标功能和任何数量的装饰器作为参数;
  • 装饰功能的名称可以与原始功能名称相等。
  • 装饰器可以决定在中执行一些操作,最后
  • ...
let logger = console.log;

// Generic decorator function
function decorate(target, ...decorators) {
    return decorators.reduce((acc, decorator) =>
        // Propagate target function name
        Object.defineProperty(decorator(acc), "name", { 
            value: target.name
        })
    , target);
}

// Decorator
function logged(task) {
    return function(...args) {
        logger("starting " + task.name);
        let result;
        try {
            result = task(...args);
        } finally { // Also execute in case of error
            logger("ending " + task.name);
        }
        return result;
    };
}

// Your function -- unaware of logger
function doSomething(arg) {
  console.log("Middle of doing something with argument " + arg);
  return arg.toUpperCase();
}

// Decorating with two (same) decorators...
doSomething = decorate(doSomething, logged, logged);

// Run...
let result = doSomething("test");
console.log(result);

I've considered ... Encapsulating ... with a decorator ...

That's the way to go.

Sending an event at the end of doSomething to notify logger it finished and using async code...

Using asynchronous code is only relevant when you already have an asynchronous API involved in doSomething.

Is there a solution that does not involve doSomething telling logger when it has finished?

That's the basic idea of decorators.

Example:

let logger = console.log;

// Decorator
function logged(task) {
    return function(...args) {
        logger("starting " + task.name);
        const result = task(...args);
        logger("ending " + task.name);
        return result;
    };
}

// Your function -- unaware of logger
function doSomething(arg) {
  console.log("Middle of doing something with argument " + arg);
  return arg.toUpperCase();
}

// Decorating...
doSomething = logged(doSomething);

// Run...
let result = doSomething("test");
console.log(result);

More features...

There are many extensions/features you can add. For instance:

  • Include a generic decorator function that takes the target function and any number of decorators as arguments;
  • The name of the decorated function could be made equal to the original function name;
  • A decorator can decide to perform some action in a finally block
  • ...

let logger = console.log;

// Generic decorator function
function decorate(target, ...decorators) {
    return decorators.reduce((acc, decorator) =>
        // Propagate target function name
        Object.defineProperty(decorator(acc), "name", { 
            value: target.name
        })
    , target);
}

// Decorator
function logged(task) {
    return function(...args) {
        logger("starting " + task.name);
        let result;
        try {
            result = task(...args);
        } finally { // Also execute in case of error
            logger("ending " + task.name);
        }
        return result;
    };
}

// Your function -- unaware of logger
function doSomething(arg) {
  console.log("Middle of doing something with argument " + arg);
  return arg.toUpperCase();
}

// Decorating with two (same) decorators...
doSomething = decorate(doSomething, logged, logged);

// Run...
let result = doSomething("test");
console.log(result);

装纯掩盖桑 2025-02-06 13:42:14

装饰器处理同步和异步函数

您可以创建一个装饰函数,该功能可以处理同步和异步功能。

在下面的摘要中, logger 是一种装饰函数,它具有作为参数的功能,并返回处理日志记录的装饰函数。

如果输入函数返回 Promise ,则使用 “ rel =“ nofollow noreferrer”> Promise.prototype.finally

const logger =
  (func) =>
  (...args) => {
    console.log(`Started: ${func.name}`);
    try {
      const ret = func(...args);
      if (ret instanceof Promise) {
        return ret.finally(() => console.log(`Ended: ${func.name}`));
      }
      console.log(`Ended: ${func.name}`);
      return ret;
    } catch (err) {
      console.log(`Ended: ${func.name}`);
      throw err;
    }
  };

const sleep = (timer) => new Promise((res) => setTimeout(res, timer));

const asyncAdd = logger(async function asyncAdd(a, b) {
  await sleep(1000).then();
  console.log(`Adding ${a} and ${b}`);
  return a + b;
});

const syncSubtraction = logger(function syncSubtraction(a, b) {
  console.log(`Subtracting ${b} from ${a}`);
  return a - b;
});

console.log(`Result of syncSubtraction(2, 1): ${syncSubtraction(2, 1)}`);
console.log("=========================");
asyncAdd(1, 2).then((res) => console.log(`Result of asyncAdd(1, 2): ${res}`));
.as-console-wrapper { max-height: 100% !important; top: 0; }

注意:

  1. 使用最后方法确保即使装饰了异步功能的“ ended” log即使失败了。

  2. 尝试...捕获块可确保同一件事,但对于同步函数。

const logger =
  (func) =>
  (...args) => {
    console.log(`Started: ${func.name}`);
    try {
      const ret = func(...args);
      if (ret instanceof Promise) {
        return ret.finally(() => console.log(`Ended: ${func.name}`));
      }
      console.log(`Ended: ${func.name}`);
      return ret;
    } catch (err) {
      console.log(`Ended: ${func.name}`);
      throw err;
    }
  };


const syncFail = logger(function syncFail() {
  console.log("Inside syncFail");
  throw new Error("Error in syncFail");
});

const sleep = (timer) => new Promise((res) => setTimeout(res, timer));

const asyncFail = logger(async function asyncFail() {
  await sleep(1000);
  console.log("Inside asyncFail");
  throw new Error("Error in asyncFail");
});

try {
  syncFail();
} catch (err) {
  console.log(err.message);
}
console.log("==========================");
asyncFail().catch((err) => console.log(err.message));
.as-console-wrapper { max-height: 100% !important; top: 0; }

处理错误案例的方式不同:

如果要处理错误方案的处理方式与成功方案不同,例如,通过打印错误日志而不是结束日志,请参阅下面的摘要:

const logger =
  (func) =>
  (...args) => {
    console.log(`Started: ${func.name}`);
    try {
      const ret = func(...args);
      if (ret instanceof Promise) {
        return ret
          .then((res) => {
            console.log(`Ended: ${func.name}`);
            return res;
          })
          .catch((err) => {
            console.log(`Errored: ${func.name}`);
            throw err;
          });
      }
      console.log(`Ended: ${func.name}`);
      return ret;
    } catch (err) {
      console.log(`Errored: ${func.name}`);
      throw err;
    }
  };

const syncFail = logger(function syncFail() {
  console.log("Inside syncFail");
  throw new Error("Error in syncFail");
});

const sleep = (timer) => new Promise((res) => setTimeout(res, timer));

const asyncFail = logger(async function asyncFail() {
  await sleep(1000);
  console.log("Inside asyncFail");
  throw new Error("Error in asyncFail");
});

try {
  syncFail();
} catch (err) {
  console.log(err.message);
}
console.log("==========================");
asyncFail().catch((err) => console.log(err.message));
.as-console-wrapper { max-height: 100% !important; top: 0; }

成功情景的行为没有改变:

const logger =
  (func) =>
  (...args) => {
    console.log(`Started: ${func.name}`);
    try {
      const ret = func(...args);
      if (ret instanceof Promise) {
        return ret
          .then((res) => {
            console.log(`Ended: ${func.name}`);
            return res;
          })
          .catch((err) => {
            console.log(`Errored: ${func.name}`);
            throw err;
          });
      }
      console.log(`Ended: ${func.name}`);
      return ret;
    } catch (err) {
      console.log(`Errored: ${func.name}`);
      throw err;
    }
  };

const sleep = (timer) => new Promise((res) => setTimeout(res, timer));

const asyncAdd = logger(async function asyncAdd(a, b) {
  await sleep(1000).then();
  console.log(`Adding ${a} and ${b}`);
  return a + b;
});

const syncSubtraction = logger(function syncSubtraction(a, b) {
  console.log(`Subtracting ${b} from ${a}`);
  return a - b;
});

console.log(`Result of syncSubtraction(2, 1): ${syncSubtraction(2, 1)}`);
console.log("=========================");
asyncAdd(1, 2).then((res) => console.log(`Result of asyncAdd(1, 2): ${res}`));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Decorator handling both synchronous and asynchronous functions

You can create a decorator function that handles both synchronous and asynchronous functions.

In the snippet below, logger is a decorator function that takes in a function as it's argument and returns a decorated function that handles the logging.

If the input function returns a Promise, it's handled using Promise.prototype.finally.

const logger =
  (func) =>
  (...args) => {
    console.log(`Started: ${func.name}`);
    try {
      const ret = func(...args);
      if (ret instanceof Promise) {
        return ret.finally(() => console.log(`Ended: ${func.name}`));
      }
      console.log(`Ended: ${func.name}`);
      return ret;
    } catch (err) {
      console.log(`Ended: ${func.name}`);
      throw err;
    }
  };

const sleep = (timer) => new Promise((res) => setTimeout(res, timer));

const asyncAdd = logger(async function asyncAdd(a, b) {
  await sleep(1000).then();
  console.log(`Adding ${a} and ${b}`);
  return a + b;
});

const syncSubtraction = logger(function syncSubtraction(a, b) {
  console.log(`Subtracting ${b} from ${a}`);
  return a - b;
});

console.log(`Result of syncSubtraction(2, 1): ${syncSubtraction(2, 1)}`);
console.log("=========================");
asyncAdd(1, 2).then((res) => console.log(`Result of asyncAdd(1, 2): ${res}`));
.as-console-wrapper { max-height: 100% !important; top: 0; }

NOTE:

  1. Using the finally method ensures that the "Ended" log is printed even if the async function being decorated fails.

  2. The try...catch block ensures the same thing but for synchronous functions.

const logger =
  (func) =>
  (...args) => {
    console.log(`Started: ${func.name}`);
    try {
      const ret = func(...args);
      if (ret instanceof Promise) {
        return ret.finally(() => console.log(`Ended: ${func.name}`));
      }
      console.log(`Ended: ${func.name}`);
      return ret;
    } catch (err) {
      console.log(`Ended: ${func.name}`);
      throw err;
    }
  };


const syncFail = logger(function syncFail() {
  console.log("Inside syncFail");
  throw new Error("Error in syncFail");
});

const sleep = (timer) => new Promise((res) => setTimeout(res, timer));

const asyncFail = logger(async function asyncFail() {
  await sleep(1000);
  console.log("Inside asyncFail");
  throw new Error("Error in asyncFail");
});

try {
  syncFail();
} catch (err) {
  console.log(err.message);
}
console.log("==========================");
asyncFail().catch((err) => console.log(err.message));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Handling error cases differently:

If you want to handle the error scenario differently than the success one, for example by printing an error log instead of the end log, then refer to the snippet below:

const logger =
  (func) =>
  (...args) => {
    console.log(`Started: ${func.name}`);
    try {
      const ret = func(...args);
      if (ret instanceof Promise) {
        return ret
          .then((res) => {
            console.log(`Ended: ${func.name}`);
            return res;
          })
          .catch((err) => {
            console.log(`Errored: ${func.name}`);
            throw err;
          });
      }
      console.log(`Ended: ${func.name}`);
      return ret;
    } catch (err) {
      console.log(`Errored: ${func.name}`);
      throw err;
    }
  };

const syncFail = logger(function syncFail() {
  console.log("Inside syncFail");
  throw new Error("Error in syncFail");
});

const sleep = (timer) => new Promise((res) => setTimeout(res, timer));

const asyncFail = logger(async function asyncFail() {
  await sleep(1000);
  console.log("Inside asyncFail");
  throw new Error("Error in asyncFail");
});

try {
  syncFail();
} catch (err) {
  console.log(err.message);
}
console.log("==========================");
asyncFail().catch((err) => console.log(err.message));
.as-console-wrapper { max-height: 100% !important; top: 0; }

There's no change in the behavior of the success scenarios:

const logger =
  (func) =>
  (...args) => {
    console.log(`Started: ${func.name}`);
    try {
      const ret = func(...args);
      if (ret instanceof Promise) {
        return ret
          .then((res) => {
            console.log(`Ended: ${func.name}`);
            return res;
          })
          .catch((err) => {
            console.log(`Errored: ${func.name}`);
            throw err;
          });
      }
      console.log(`Ended: ${func.name}`);
      return ret;
    } catch (err) {
      console.log(`Errored: ${func.name}`);
      throw err;
    }
  };

const sleep = (timer) => new Promise((res) => setTimeout(res, timer));

const asyncAdd = logger(async function asyncAdd(a, b) {
  await sleep(1000).then();
  console.log(`Adding ${a} and ${b}`);
  return a + b;
});

const syncSubtraction = logger(function syncSubtraction(a, b) {
  console.log(`Subtracting ${b} from ${a}`);
  return a - b;
});

console.log(`Result of syncSubtraction(2, 1): ${syncSubtraction(2, 1)}`);
console.log("=========================");
asyncAdd(1, 2).then((res) => console.log(`Result of asyncAdd(1, 2): ${res}`));
.as-console-wrapper { max-height: 100% !important; top: 0; }

残疾 2025-02-06 13:42:14

我将与 @trincot的解决方案相互补充,并使用异步代码处理。

它可以等到异步任务完成并且包装功能对装饰器一无所知

const logStarting = (taskName) => console.log("starting " + taskName);

const logFinishing = (taskName) => console.log('ending ' + taskName);

const logFailed = (taskName, e) => console.error('The task: ' + taskName + ' has failed with an error: ', e);

function logged(task) {
  const taskName = task.name;
  if (task.constructor.name === 'AsyncFunction') {
    return async function(...args) {
      logStarting(taskName);
      try {
        const result = await task(...args);
        logFinishing(taskName);
        return result;
      } catch(e) {
        logFailed(taskName, e);
        thrhow e;
      }
    }
  }
  
  return function(...args) {
    logStarting(taskName);
    try {
      const result = task(...args);
      logFinishing(taskName);
      return result;
    } catch(e) {
       logFailed(taskName, e);
       thrhow e;
    }
  };
}

I'll just complement @trincot's solution with the asynchronous code handling.

It can wait until the async task will be finished and the wrapped function doesn't know anything about the decorator

const logStarting = (taskName) => console.log("starting " + taskName);

const logFinishing = (taskName) => console.log('ending ' + taskName);

const logFailed = (taskName, e) => console.error('The task: ' + taskName + ' has failed with an error: ', e);

function logged(task) {
  const taskName = task.name;
  if (task.constructor.name === 'AsyncFunction') {
    return async function(...args) {
      logStarting(taskName);
      try {
        const result = await task(...args);
        logFinishing(taskName);
        return result;
      } catch(e) {
        logFailed(taskName, e);
        thrhow e;
      }
    }
  }
  
  return function(...args) {
    logStarting(taskName);
    try {
      const result = task(...args);
      logFinishing(taskName);
      return result;
    } catch(e) {
       logFailed(taskName, e);
       thrhow e;
    }
  };
}

三生池水覆流年 2025-02-06 13:42:14

trincot som shekhar mukherjee and dzianis roi 可以看作是一种特殊的装饰函数的一部分,该功能描述了拦截和更改函数或方法的控制流的良好定义的用例,无论这些函数/方法是否异步。

我更喜欢调用此类函数。它们是自己的方法,在 function.protype asyncfunction.protype 上分别实现,这允许他们的简单用法,因为一个人不需要过多地考虑如何过多考虑实现异步或非迅速函数/方法的正确包装。一个人只需要通过修改器方法选择正确/预期的用例,例如 围绕 ,“ nofollow noreferrer”> afterThrowing,

OP的用例是最明显的用例,将其他功能包装在一个给定的功能/方法周围,因此无法重构/重写)。下一个提供的示例代码显示了如何通过修饰符的处理程序功能围绕进行更轻松的代码,以便完全控制 mockedfetch 的调用及其结果是要处理...

async function mockedFetch(...args) {
  console.log('`mockedFetch` invoked with args ...', args);

  return new Promise(resolve => setTimeout(resolve, 3000, 'successfully fetched result'));
}

const fetchLogger = mockedFetch
  .around(async function aroundHandler (proceed, handler, ...args) {

    console.log(`inside the around modification handler \`${ handler.name }\``);

    console.log(`immediate before the invocation of \`${ proceed.name }\` with args ...`, args);

    const result = await proceed(...args);

    console.log(`immediate after the returning of \`${ proceed.name }\` with result ... '${ result }'`);

    return result;
  });


(async () => {
  console.log(`\`mockedFetch.name\` ... '${ mockedFetch.name }'`);
  console.log(`\`fetchLogger.name\` ... '${ fetchLogger.name }'`);
  console.log('\n');

  console.log('invoke `fetchLogger` with args ...', ['foo', 'bar', 'baz'], '\n... now ...');
  console.log('\n');

  const result = await fetchLogger('foo', 'bar', 'baz');

  console.log('\n');
  console.log(`awaited \`fetchLogger\` result ... '${ result }'`);
})();
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
(function () {

  // BEGIN :: module scope
  'use strict';


  /* function type specifc detection helpers */


  /**
   * Detects whether a passed function type features a truly `writable`
   * `prototype` property.
   *
   * @param {Function} value
   *  Assumes a `'function'` type, but does not check for it.
   * @returns {boolean}
   *  Returns whether the passed value features a truly `writable`
   *  `prototype` property.
   */
  function hasWritablePrototype(value) {
    return Object.getOwnPropertyDescriptor(value, 'prototype')?.writable === true;
  }

  /**
   * Reaches for any value's built-in type-signature by making use of ...
   * 
   * ```
   * Object.prototype.toString.call(value);
   * ```
   *
   * ... which helps avoiding possibly
   * manipulated `toString` behavior.
   *
   * @param {any} value
   *  The to be processed value.
   * @returns {string}
   *  Returns the value's built-in type-signature
   *  like e.g. `'[object Date]'`.
   */
  function getBuiltInTypeSignature(value) {
    return Object.prototype.toString.call(value).trim();
  }

  /**
   * Reaches for a function's true stringified version by making use of ...
   * 
   * ```
   * Function.prototype.toString.call(value);
   * ```
   *
   * ... which helps avoiding possibly
   * manipulated `toString` behavior.
   *
   * @param {Function} value
   *  Assumes a `'function'` type, but does not check for it.
   * @returns {string}
   *  Returns a function's true/real stringified implementation.
   */
  function getFunctionSignature(value) {
    return Function.prototype.toString.call(value).trim();
  }

  /**
   * Reaches for a function's direct/immediate constructor-function.
   *
   * @param {Function} value
   *  Assumes a `'function'` type, but does not check for it.
   * @returns {string}
   *  Returns a function's direct/immediate constructor-function.
   */
  function getConstructorFunction(value) {
    return Reflect.getOwnPropertyDescriptor(

      Object.getPrototypeOf(value), 'constructor'
    )
    .value;
  }
  const AsyncFunction = getConstructorFunction(async function () {});


  /**
   * Detects any function type, which is ...
   *
   * ... the `typeof` operator not only returns the `'function'` string
   * for the processed `value`, but the latter also features both of a
   * function's call methods `call` and `apply`.
   *
   * @param {any} value
   *  The to be processed value.
   * @returns {boolean}
   *  A boolean value which indicates whether the tested value is a function.
   */
  function isFunction(value) {
    return (
      typeof value === 'function' &&
      typeof value.call === 'function' &&
      typeof value.apply === 'function'
    );
  }

  /**
   * Detects whether the passed `value` is any kind of arrow function,
   * either async or not.
   *
   * @param {any} value
   *  The to be processed value.
   * @returns {boolean}
   *  A boolean value which indicates whether the tested value is either
   *  kind of arrow function.
   */
  function isArrowFunctionType(value) {
    return (
      isFunction(value) &&
      /^(?:async\s*)?(?:\(.*?\)|[^(),=]+)\s*=>/.test(getFunctionSignature(value))
    );
  }

  /**
   * Detects whether the passed `value` is any kind of (non generator) async function,
   *  - either async arrow (expression)
   *  - or async function expression
   *  - or async function statement.
   *
   * @param {any} value
   *  The to be processed value.
   * @returns {boolean}
   *  A boolean value which indicates whether the tested value is an async function.
   */
  function isAsyncFunction(value) {
    return !!value && getBuiltInTypeSignature(value) === '[object AsyncFunction]';
  }

  /**
   * Detects whether the passed `value` is any kind of generator function,
   * either async or not.
   *
   * @param {any} value
   *  The to be processed value.
   * @returns {boolean}
   *  A boolean value which indicates whether the tested value is either
   *  kind of generator function.
   */
  function isGeneratorFunctionType(value) {
    const signature = !!value && getBuiltInTypeSignature(value);
    return signature && (
      signature === '[object GeneratorFunction]' ||
      signature === '[object AsyncGeneratorFunction]'
    );
  }

  /**
   * Detects whether the passed `value` is exclusively the
   * only known function type as far back as with/at ES3
   * (in addition to all the built-in constructor functions).
   *
   * @param {any} value
   *  The to be processed value.
   * @returns {boolean}
   *  A boolean value which indicates whether the tested value
   *  is exclusively the only known function type back at ES3.
   */
  function isES3Function(value) {
    return (
      isFunction(value) &&

      hasWritablePrototype(value) &&
      !isGeneratorFunctionType(value) &&

      // - detects any instance of a class that extends `Function`.
      !getFunctionSignature(getConstructorFunction(value)).startsWith('class ')
    );
  }


  /* modifier specifc reflection and configuration helpers */


  /**
   * Assumes the passed function type to be a modified function and
   * does augment it with modified function specific behavior/traits
   * which are ...
   *
   *  - a changed function `name` property
   *    with a modification specific prefix,
   *  - an additional `origin` property which
   *    refers to the original/unmodified function,
   *  - an additional `handler` property which refers
   *    to the modification specific handler function.
   *
   * @param {string} modifierName
   *  The specific modifier name which becomes the prefix part of the
   *  function's changed name.
   * @param {Function} origin
   *  The original/unmodified function's reference.
   * @param {Function} handler
   *  The reference of the modification specific handler function.
   * @param {Function} modified
   *  The modified `'function'` type (assumed, but not checked for).
   * @returns {Function}
   *  Returns the passed and augmented modified function.
   */
  function asConfiguredModification(modifierName, origin, handler, modified) {
    const nameDescriptor = Reflect.getOwnPropertyDescriptor(origin, 'name');

    Reflect.defineProperty(modified, 'name', {
      ...nameDescriptor,
      value: `modified::${ modifierName } ${ nameDescriptor.value }`,
    });
    Reflect.defineProperty(modified, 'origin', { value: origin });
    Reflect.defineProperty(modified, 'handler', { value: handler });

    // Reflect.defineProperty(modified, 'origin', { get: () => origin });
    // Reflect.defineProperty(modified, 'handler', { get: () => handler });

    return modified;
  }
  const modifierConfig = { writable: true, configurable: true };


  /* all modifier specifc implementations */


  /* function type validation guards for any modifier specific implementation */

  function runThrowingBaseValidationGuard(modifierName, proceed, handler, isAsync = false) {
    if (!isFunction(proceed)) {
      throw new TypeError([
        'The value delegated to',
        `\`${ isAsync && 'Async' || '' }Function.prototype.${ modifierName }\``,
        'needs to be at least a `Function` instance.',
      ].join(' '));
    }
    if (!isFunction(handler)) {
      throw new TypeError([
        'The', `${ isAsync && 'asynchronous ' || '' }\`${ modifierName }\``,
        'modifier\'s 1st `handler` parameter has to be at least a `Function` instance.',
      ].join(' '));
    }
  }
  function runThrowingAsyncValidationGuard(modifierName, proceed, handler) {
    runThrowingBaseValidationGuard(modifierName, proceed, handler, true);
    if (
      !isAsyncFunction(proceed) &&
      !isAsyncFunction(handler)
    ) {
      throw new TypeError([
        'In case the value delegated to', `\`AsyncFunction.prototype.${ modifierName }\``,
        'is a non asynchronous function type, the asynchronous', `\`${ modifierName }\``,
        'modifier\'s 1st `handler` parameter then has to be exclusively an `AsyncFunction`',
        'instance.',
      ].join(' '));
    }
    if (
      isAsyncFunction(proceed) &&

      !isAsyncFunction(handler) &&
      !isArrowFunctionType(handler) &&
      !isES3Function(handler)
    ) {
      throw new TypeError([
        'In case of modifying an asynchronous function type, the', `\`${ modifierName }\``,
        'modifier\'s 1st `handler` parameter has to be either an instance of `AsyncFunction`',
        'or an arrow function expression or an ES3 function type.',
      ].join(' '));
    }
  }


  /* +++ AROUND +++ and +++ ASYNC AROUND +++ */

  // - The modified function enables full control over the original function's
  //   control flow by providing access to everything, the original function,
  //   the custom implemented around handler itself, the call context and the
  //   passed arguments. Thus, the modified function's return value is determined
  //   exclusively by the around handler's implementation.

  function aroundModifier(handler, target) {
    'use strict';
    // see ... [https://github.com/tc39/proposal-function-implementation-hiding]
    'hide source';

    const proceed = this;
    runThrowingBaseValidationGuard('around', proceed, handler);

    target = target ?? null;
    return (

      isAsyncFunction(proceed) ||
      isAsyncFunction(handler)

      // delegate the modification to the async `around` implementation.
    ) && AsyncFunction.prototype.around.call(proceed, handler, target) ||

    asConfiguredModification(
      'around', proceed, handler,

      function /* aroundType */(...args) {

        return handler.call((this ?? target), proceed, handler, ...args);
      },
    );
  }
  function asyncAroundModifier(handler, target) {
    'use strict';
    // see ... [https://github.com/tc39/proposal-function-implementation-hiding]
    'hide source';

    const proceed = this;
    runThrowingAsyncValidationGuard('around', proceed, handler);

    target = target ?? null;

    return asConfiguredModification(
      'around', proceed, handler,

      async function /* asyncAroundType */(...args) {

        return await handler.call((this ?? target), proceed, handler, ...args);
      },
    );
  }
  Reflect.defineProperty(Function.prototype, 'around', {
    ...modifierConfig, value: aroundModifier,
  });
  Reflect.defineProperty(AsyncFunction.prototype, 'around', {
    ...modifierConfig, value: asyncAroundModifier,
  });


  // END :: module scope.

}());

// console.log(Function.prototype.afterFinally);
// console.log((async function () {}).constructor.prototype.afterFinally);
</script>

The approaches and implementations of trincot and Som Shekhar Mukherjee and Dzianis Roi can be seen as a part of a special kind of decorator functions which describe well defined use cases of intercepting and altering either a function's or a method's control flow, regardless of whether these function's/method's are async or not.

I prefer calling such functions "method modifiers". They are methods themself, implemented at Function.prototype respectively AsyncFunction.prototype, which allows theirs easy usage, because one does not need to think too much about how to implement the correct wrapping of an async or non-async function/method. One just needs to pick the right/intended use case via a modifier method like around, before, after, afterThrowing, afterFinally.

The OP's use case is the most obvious one, wrapping additional functionality around a given function/method which one does not own (hence can not refactor/rewrite). The next provided example code shows how easy one can apply additional code via the around modifier's handler function in order to get the full control over how the invocation of mockedFetch and its result is going to be handled ...

async function mockedFetch(...args) {
  console.log('`mockedFetch` invoked with args ...', args);

  return new Promise(resolve => setTimeout(resolve, 3000, 'successfully fetched result'));
}

const fetchLogger = mockedFetch
  .around(async function aroundHandler (proceed, handler, ...args) {

    console.log(`inside the around modification handler \`${ handler.name }\``);

    console.log(`immediate before the invocation of \`${ proceed.name }\` with args ...`, args);

    const result = await proceed(...args);

    console.log(`immediate after the returning of \`${ proceed.name }\` with result ... '${ result }'`);

    return result;
  });


(async () => {
  console.log(`\`mockedFetch.name\` ... '${ mockedFetch.name }'`);
  console.log(`\`fetchLogger.name\` ... '${ fetchLogger.name }'`);
  console.log('\n');

  console.log('invoke `fetchLogger` with args ...', ['foo', 'bar', 'baz'], '\n... now ...');
  console.log('\n');

  const result = await fetchLogger('foo', 'bar', 'baz');

  console.log('\n');
  console.log(`awaited \`fetchLogger\` result ... '${ result }'`);
})();
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
(function () {

  // BEGIN :: module scope
  'use strict';


  /* function type specifc detection helpers */


  /**
   * Detects whether a passed function type features a truly `writable`
   * `prototype` property.
   *
   * @param {Function} value
   *  Assumes a `'function'` type, but does not check for it.
   * @returns {boolean}
   *  Returns whether the passed value features a truly `writable`
   *  `prototype` property.
   */
  function hasWritablePrototype(value) {
    return Object.getOwnPropertyDescriptor(value, 'prototype')?.writable === true;
  }

  /**
   * Reaches for any value's built-in type-signature by making use of ...
   * 
   * ```
   * Object.prototype.toString.call(value);
   * ```
   *
   * ... which helps avoiding possibly
   * manipulated `toString` behavior.
   *
   * @param {any} value
   *  The to be processed value.
   * @returns {string}
   *  Returns the value's built-in type-signature
   *  like e.g. `'[object Date]'`.
   */
  function getBuiltInTypeSignature(value) {
    return Object.prototype.toString.call(value).trim();
  }

  /**
   * Reaches for a function's true stringified version by making use of ...
   * 
   * ```
   * Function.prototype.toString.call(value);
   * ```
   *
   * ... which helps avoiding possibly
   * manipulated `toString` behavior.
   *
   * @param {Function} value
   *  Assumes a `'function'` type, but does not check for it.
   * @returns {string}
   *  Returns a function's true/real stringified implementation.
   */
  function getFunctionSignature(value) {
    return Function.prototype.toString.call(value).trim();
  }

  /**
   * Reaches for a function's direct/immediate constructor-function.
   *
   * @param {Function} value
   *  Assumes a `'function'` type, but does not check for it.
   * @returns {string}
   *  Returns a function's direct/immediate constructor-function.
   */
  function getConstructorFunction(value) {
    return Reflect.getOwnPropertyDescriptor(

      Object.getPrototypeOf(value), 'constructor'
    )
    .value;
  }
  const AsyncFunction = getConstructorFunction(async function () {});


  /**
   * Detects any function type, which is ...
   *
   * ... the `typeof` operator not only returns the `'function'` string
   * for the processed `value`, but the latter also features both of a
   * function's call methods `call` and `apply`.
   *
   * @param {any} value
   *  The to be processed value.
   * @returns {boolean}
   *  A boolean value which indicates whether the tested value is a function.
   */
  function isFunction(value) {
    return (
      typeof value === 'function' &&
      typeof value.call === 'function' &&
      typeof value.apply === 'function'
    );
  }

  /**
   * Detects whether the passed `value` is any kind of arrow function,
   * either async or not.
   *
   * @param {any} value
   *  The to be processed value.
   * @returns {boolean}
   *  A boolean value which indicates whether the tested value is either
   *  kind of arrow function.
   */
  function isArrowFunctionType(value) {
    return (
      isFunction(value) &&
      /^(?:async\s*)?(?:\(.*?\)|[^(),=]+)\s*=>/.test(getFunctionSignature(value))
    );
  }

  /**
   * Detects whether the passed `value` is any kind of (non generator) async function,
   *  - either async arrow (expression)
   *  - or async function expression
   *  - or async function statement.
   *
   * @param {any} value
   *  The to be processed value.
   * @returns {boolean}
   *  A boolean value which indicates whether the tested value is an async function.
   */
  function isAsyncFunction(value) {
    return !!value && getBuiltInTypeSignature(value) === '[object AsyncFunction]';
  }

  /**
   * Detects whether the passed `value` is any kind of generator function,
   * either async or not.
   *
   * @param {any} value
   *  The to be processed value.
   * @returns {boolean}
   *  A boolean value which indicates whether the tested value is either
   *  kind of generator function.
   */
  function isGeneratorFunctionType(value) {
    const signature = !!value && getBuiltInTypeSignature(value);
    return signature && (
      signature === '[object GeneratorFunction]' ||
      signature === '[object AsyncGeneratorFunction]'
    );
  }

  /**
   * Detects whether the passed `value` is exclusively the
   * only known function type as far back as with/at ES3
   * (in addition to all the built-in constructor functions).
   *
   * @param {any} value
   *  The to be processed value.
   * @returns {boolean}
   *  A boolean value which indicates whether the tested value
   *  is exclusively the only known function type back at ES3.
   */
  function isES3Function(value) {
    return (
      isFunction(value) &&

      hasWritablePrototype(value) &&
      !isGeneratorFunctionType(value) &&

      // - detects any instance of a class that extends `Function`.
      !getFunctionSignature(getConstructorFunction(value)).startsWith('class ')
    );
  }


  /* modifier specifc reflection and configuration helpers */


  /**
   * Assumes the passed function type to be a modified function and
   * does augment it with modified function specific behavior/traits
   * which are ...
   *
   *  - a changed function `name` property
   *    with a modification specific prefix,
   *  - an additional `origin` property which
   *    refers to the original/unmodified function,
   *  - an additional `handler` property which refers
   *    to the modification specific handler function.
   *
   * @param {string} modifierName
   *  The specific modifier name which becomes the prefix part of the
   *  function's changed name.
   * @param {Function} origin
   *  The original/unmodified function's reference.
   * @param {Function} handler
   *  The reference of the modification specific handler function.
   * @param {Function} modified
   *  The modified `'function'` type (assumed, but not checked for).
   * @returns {Function}
   *  Returns the passed and augmented modified function.
   */
  function asConfiguredModification(modifierName, origin, handler, modified) {
    const nameDescriptor = Reflect.getOwnPropertyDescriptor(origin, 'name');

    Reflect.defineProperty(modified, 'name', {
      ...nameDescriptor,
      value: `modified::${ modifierName } ${ nameDescriptor.value }`,
    });
    Reflect.defineProperty(modified, 'origin', { value: origin });
    Reflect.defineProperty(modified, 'handler', { value: handler });

    // Reflect.defineProperty(modified, 'origin', { get: () => origin });
    // Reflect.defineProperty(modified, 'handler', { get: () => handler });

    return modified;
  }
  const modifierConfig = { writable: true, configurable: true };


  /* all modifier specifc implementations */


  /* function type validation guards for any modifier specific implementation */

  function runThrowingBaseValidationGuard(modifierName, proceed, handler, isAsync = false) {
    if (!isFunction(proceed)) {
      throw new TypeError([
        'The value delegated to',
        `\`${ isAsync && 'Async' || '' }Function.prototype.${ modifierName }\``,
        'needs to be at least a `Function` instance.',
      ].join(' '));
    }
    if (!isFunction(handler)) {
      throw new TypeError([
        'The', `${ isAsync && 'asynchronous ' || '' }\`${ modifierName }\``,
        'modifier\'s 1st `handler` parameter has to be at least a `Function` instance.',
      ].join(' '));
    }
  }
  function runThrowingAsyncValidationGuard(modifierName, proceed, handler) {
    runThrowingBaseValidationGuard(modifierName, proceed, handler, true);
    if (
      !isAsyncFunction(proceed) &&
      !isAsyncFunction(handler)
    ) {
      throw new TypeError([
        'In case the value delegated to', `\`AsyncFunction.prototype.${ modifierName }\``,
        'is a non asynchronous function type, the asynchronous', `\`${ modifierName }\``,
        'modifier\'s 1st `handler` parameter then has to be exclusively an `AsyncFunction`',
        'instance.',
      ].join(' '));
    }
    if (
      isAsyncFunction(proceed) &&

      !isAsyncFunction(handler) &&
      !isArrowFunctionType(handler) &&
      !isES3Function(handler)
    ) {
      throw new TypeError([
        'In case of modifying an asynchronous function type, the', `\`${ modifierName }\``,
        'modifier\'s 1st `handler` parameter has to be either an instance of `AsyncFunction`',
        'or an arrow function expression or an ES3 function type.',
      ].join(' '));
    }
  }


  /* +++ AROUND +++ and +++ ASYNC AROUND +++ */

  // - The modified function enables full control over the original function's
  //   control flow by providing access to everything, the original function,
  //   the custom implemented around handler itself, the call context and the
  //   passed arguments. Thus, the modified function's return value is determined
  //   exclusively by the around handler's implementation.

  function aroundModifier(handler, target) {
    'use strict';
    // see ... [https://github.com/tc39/proposal-function-implementation-hiding]
    'hide source';

    const proceed = this;
    runThrowingBaseValidationGuard('around', proceed, handler);

    target = target ?? null;
    return (

      isAsyncFunction(proceed) ||
      isAsyncFunction(handler)

      // delegate the modification to the async `around` implementation.
    ) && AsyncFunction.prototype.around.call(proceed, handler, target) ||

    asConfiguredModification(
      'around', proceed, handler,

      function /* aroundType */(...args) {

        return handler.call((this ?? target), proceed, handler, ...args);
      },
    );
  }
  function asyncAroundModifier(handler, target) {
    'use strict';
    // see ... [https://github.com/tc39/proposal-function-implementation-hiding]
    'hide source';

    const proceed = this;
    runThrowingAsyncValidationGuard('around', proceed, handler);

    target = target ?? null;

    return asConfiguredModification(
      'around', proceed, handler,

      async function /* asyncAroundType */(...args) {

        return await handler.call((this ?? target), proceed, handler, ...args);
      },
    );
  }
  Reflect.defineProperty(Function.prototype, 'around', {
    ...modifierConfig, value: aroundModifier,
  });
  Reflect.defineProperty(AsyncFunction.prototype, 'around', {
    ...modifierConfig, value: asyncAroundModifier,
  });


  // END :: module scope.

}());

// console.log(Function.prototype.afterFinally);
// console.log((async function () {}).constructor.prototype.afterFinally);
</script>

來不及說愛妳 2025-02-06 13:42:14

好的,因此在了解C#的垃圾收集器之后,我偶然发现了一些使我想起这个问题的东西。这似乎是解决方案:

const registerFinalizer = new FinalizationRegistry(message => {
  console.log(message)
});

function logger() {
  console.log("started function")
  registerFinalizer.register(logger.caller, "finished running")
}

function doSomething() {
  logger()
  console.log("doing stuff")
}


doSomething()

setInterval(function() {

}, 1000)

contrizationRegistry 基本上观察该功能并在垃圾收集器删除对象后调用回调,在这种情况下,在函数完成执行后的某个时间发生了该对象。

这可能是面试官检查我对垃圾收集器的了解的方式,这似乎不是JavaScript中的一个流行主题,如果不查看另一种语言,我永远不会找到这种解决方案。

另外,显然它与记录没有什么关系,这可能是一种登录的不良方法,它记录了函数 dosomething 已经完成了很大的延迟运行,因为它等待垃圾收集器来做他的事情,这就是为什么我们需要 setInterval 最后(或任何其他事情以保持过程运行)。

Ok so after learning about garbage collectors in C# I stumbled upon something that instantly reminded me of this problem. This appears to be the solution:

const registerFinalizer = new FinalizationRegistry(message => {
  console.log(message)
});

function logger() {
  console.log("started function")
  registerFinalizer.register(logger.caller, "finished running")
}

function doSomething() {
  logger()
  console.log("doing stuff")
}


doSomething()

setInterval(function() {

}, 1000)

FinalizationRegistry basically observes the function and calls the callback once the garbage collector removes the object, which in this case happens some time after the function finished execution.

It was probably the interviewer's way to check my knowledge of the garbage collector, it just doesn't seem like a popular subject in JavaScript, I would never have found this solution if not looking at another language.

Also, apparently it has nothing to do really with logging, and this is probably a bad way to log, it logs that the function doSomething has finished running in a big delay since it waits for garbage collector to do his thing, that's why we need the setInterval in the end(or any other thing to keep the process running).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文