You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

54 lines
1.5 KiB

4 years ago
  1. 'use strict';
  2. let call = module.exports = {
  3. safe: safeCall,
  4. once: callOnce,
  5. };
  6. /**
  7. * Calls a function with the given arguments, and ensures that the error-first callback is _always_
  8. * invoked exactly once, even if the function throws an error.
  9. *
  10. * @param {function} fn - The function to invoke
  11. * @param {...*} args - The arguments to pass to the function. The final argument must be a callback function.
  12. */
  13. function safeCall (fn, args) {
  14. // Get the function arguments as an array
  15. args = Array.prototype.slice.call(arguments, 1);
  16. // Replace the callback function with a wrapper that ensures it will only be called once
  17. let callback = call.once(args.pop());
  18. args.push(callback);
  19. try {
  20. fn.apply(null, args);
  21. }
  22. catch (err) {
  23. callback(err);
  24. }
  25. }
  26. /**
  27. * Returns a wrapper function that ensures the given callback function is only called once.
  28. * Subsequent calls are ignored, unless the first argument is an Error, in which case the
  29. * error is thrown.
  30. *
  31. * @param {function} fn - The function that should only be called once
  32. * @returns {function}
  33. */
  34. function callOnce (fn) {
  35. let fulfilled = false;
  36. return function onceWrapper (err) {
  37. if (!fulfilled) {
  38. fulfilled = true;
  39. return fn.apply(this, arguments);
  40. }
  41. else if (err) {
  42. // The callback has already been called, but now an error has occurred
  43. // (most likely inside the callback function). So re-throw the error,
  44. // so it gets handled further up the call stack
  45. throw err;
  46. }
  47. };
  48. }