index.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. // Import
  2. const typeChecker = require('typechecker')
  3. // Define
  4. module.exports = function ambi (method, ...args) {
  5. // Prepare
  6. let fireMethod, introspectMethod
  7. // If binding has occured then make sure we are introspecting the write method
  8. // by allowing the user to pass method as an array of two methods
  9. // the method to fire, and the method to introspect
  10. if ( typeChecker.isArray(method) ) {
  11. [fireMethod, introspectMethod] = method
  12. }
  13. else {
  14. fireMethod = introspectMethod = method
  15. }
  16. // Extract the preceeding arguments and the completion callback
  17. const simpleArguments = args.slice(0, -1)
  18. const completionCallback = args.slice(-1)[0]
  19. // Check the completion callback is actually a function
  20. if ( !typeChecker.isFunction(completionCallback) ) {
  21. throw new Error('ambi was called without a completion callback')
  22. }
  23. /*
  24. Different ways functions can be called:
  25. ambi(function(a,next){return next()}, a, next)
  26. > VALID: execute asynchronously
  27. > given arguments are SAME as the accepted arguments
  28. > method will be fired with (a, next)
  29. ambi(function(a,next){return next()}, next)
  30. > VALID: execute asynchronously
  31. > given arguments are LESS than the accepted arguments
  32. > method will be fired with (undefined, next)
  33. ambi(function(a){}, a, next)
  34. > VALID: execute synchronously
  35. > given arguments are MORE than expected arguments
  36. > method will be fired with (a)
  37. ambi(function(a){}, next)
  38. > INVALID: execute asynchronously
  39. > given arguments are SAME as the accepted arguments
  40. > method will be fired with (next)
  41. > if they want to use optional args, the function must accept a completion callback
  42. */
  43. const givenArgumentsLength = args.length
  44. const acceptedArgumentsLength = introspectMethod.length
  45. let argumentsDifferenceLength = null
  46. let executeAsynchronously = null
  47. // Given arguments are SAME as the expected arguments
  48. // This will execute asynchronously
  49. // Don't have to do anything with the arguments
  50. if ( givenArgumentsLength === acceptedArgumentsLength ) {
  51. executeAsynchronously = true
  52. }
  53. // Given arguments are LESS than the expected arguments
  54. // This will execute asynchronously
  55. // We will need to supplement any missing expected arguments with undefined
  56. // to ensure the compeltion callback is in the right place in the arguments listing
  57. else if ( givenArgumentsLength < acceptedArgumentsLength ) {
  58. executeAsynchronously = true
  59. argumentsDifferenceLength = acceptedArgumentsLength - givenArgumentsLength
  60. args = simpleArguments.slice().concat(new Array(argumentsDifferenceLength)).concat([completionCallback])
  61. }
  62. // Given arguments are MORE than the expected arguments
  63. // This will execute synchronously
  64. // We should to trim off the completion callback from the arguments
  65. // as the synchronous function won't care for it
  66. // while this isn't essential
  67. // it will provide some expectation for the user as to which mode their function was executed in
  68. else {
  69. executeAsynchronously = false
  70. args = simpleArguments.slice()
  71. }
  72. // Execute with the exceptation that the method will fire the completion callback itself
  73. if ( executeAsynchronously ) {
  74. // Fire the method
  75. fireMethod(...args)
  76. }
  77. // Execute with the expectation that we will need to fire the completion callback ourselves
  78. // Always call the completion callback ourselves as the fire method does not make use of it
  79. else {
  80. // Fire the method and check for returned errors
  81. const result = fireMethod(...args)
  82. // Check the result for a returned error
  83. if ( typeChecker.isError(result) ) {
  84. // An error was returned so fire the completion callback with the error
  85. const err = result
  86. completionCallback(err)
  87. }
  88. else {
  89. // Everything worked, so fire the completion callback without an error and with the result
  90. completionCallback(null, result)
  91. }
  92. }
  93. // Return nothing as we expect ambi to deal with synchronous and asynchronous methods
  94. // so returning something will only work for synchronous methods
  95. // and not asynchronous ones
  96. // so returning anything would be inconsistent
  97. return null
  98. }