parley.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. /**
  2. * Module dependencies
  3. */
  4. var util = require('util');
  5. var _ = require('@sailshq/lodash');
  6. var flaverr = require('flaverr');
  7. var Deferred = require('./private/Deferred');
  8. // Optimization: Pull process env check up here.
  9. var IS_DEBUG_OR_NON_PRODUCTION_ENV = (
  10. process.env.NODE_ENV !== 'production' ||
  11. process.env.DEBUG
  12. );
  13. /**
  14. * parley()
  15. *
  16. * Build a deferred object that supports Node-style callbacks and promises.
  17. * > See README.md for more details.
  18. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  19. * @param {Function} handleExec
  20. * The `handleExec` function to call (either immediately or when the Deferred
  21. * is executed, depending on whether an explicit cb was provided)
  22. *
  23. * @param {Function?} explicitCbMaybe
  24. * An optional parameter that, if specified, is passed directly as the incoming
  25. * `done` argument to your "handleExec" handler function (i.e. _its_ callback).
  26. * Otherwise, if it is omitted, then handleExec receives an internally-generated
  27. * callback (from parley) as its `done` argument. When called, this implicit `done`
  28. * will appropriately dispatch with the deferred object. Finally, note that if an
  29. * explicit callback is provided, parley will return undefined instead of returning
  30. * a Deferred.
  31. * > The nice thing about this is that it allows implementor code that provides this
  32. * > feature to avoid manually duplicating the branching logic (i.e. the code that
  33. * > checks to see if an explicit cb was provided, and if not, returns a new Deferred)
  34. *
  35. * @param {Dictionary?} customMethods
  36. * An optional dictionary of custom functions that, if specified, will be used to extend
  37. * the Deferred object. It omitted, then only the default methods like `.exec()` will
  38. * exist.
  39. * > e.g.
  40. * > ```
  41. * > {
  42. * > where: function (whereClause) {
  43. * > this._criteria = this._criteria || {};
  44. * > this._criteria.where = whereClause;
  45. * > return this;
  46. * > },
  47. * > foo: function(){...},
  48. * > bar: function(){...},
  49. * > ...
  50. * > }
  51. *
  52. * @param {Number?} timeout
  53. * Optional. If specified, timeouts will be enabled, and this number will indicate
  54. * the max # of milliseconds to let the `handleExec` logic run before giving up and
  55. * failing with a TimeoutError. (To disable timeouts, leave this undefined.)
  56. *
  57. * @param {Error?} omen
  58. * An optional omen to use for improving the stack trace, in the event of an error.
  59. *
  60. * @param {Function?} finalAfterExecLC
  61. * An optional, synchronous handler function for intercepting the arguments to .exec()'s
  62. * callback. Only applicable when using the Deferred-style usage-- including when using
  63. * promises via ES8's async/await or .then()/.catch(). Receives the `(err, result)`
  64. * function signature, where either `err` is an Error instance and `result` is undefined,
  65. * or `err` is undefined and `result` may or may not exist. In any case, if specified,
  66. * this handler function MUST not throw, and it MUST respect standard node-style callback
  67. * conventions insofar as how it decides a return value. For example, if it receives `err`,
  68. * it must return an Error instance. (ENFORCING THIS IS UP TO YOUR CODE!)
  69. * > NOTE: The purpose of this handler is to allow for changing the behavior of .exec()
  70. * > without necessarily calling it, or reinventing the wheel and creating our own version.
  71. * > For example, we might want to include nicer, more customized error messages. Or we
  72. * > might want to intercept built-in error scenarios that would be otherwise difficult to
  73. * > capture-- things like timeouts, double-invocation, and unhandled throwing.
  74. *
  75. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  76. * @returns {Deferred}
  77. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  78. * @throws {Error} If there are unexpected usage problems with how parley() itself is called
  79. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  80. */
  81. module.exports = function parley(handleExec, explicitCbMaybe, customMethods, timeout, omen, finalAfterExecLC){
  82. // A few (very carefully picked) sanity checks for implementors.
  83. //
  84. // > Note that we deliberately use `typeof` instead of _.isFunction() for performance.
  85. if (!handleExec) {
  86. throw new Error('Consistency violation: Must specify a first argument when calling parley() -- please provide a `handleExec` function or a dictionary of options');
  87. }
  88. if (typeof handleExec !== 'function') {
  89. throw new Error('Consistency violation: First argument to parley() should be a function. But instead, got: '+util.inspect(handleExec, {depth:2})+'');
  90. }
  91. //==========================================================================================
  92. // ALL OTHER **IMPLEMENTOR** USAGE CHECKS WERE REMOVED FOR PERFORMANCE REASONS.
  93. //
  94. // > Check out this commit for more of the original code:
  95. // > https://github.com/mikermcneil/parley/commit/e7ec7e445e2a502b9fcb57bc746c7b9714d3cf16
  96. // >
  97. // > Or for another example of a simple check that would pack a 24% performance hit
  98. // > for building Deferreds, see:
  99. // > https://github.com/mikermcneil/parley/commit/7d475d0c2165b683d8d5af98a4d073875f14cbd3
  100. // >
  101. // > Also note we still do a few (very carefully picked) validations for things that could
  102. // > affect end users of parley-implementing functions -- i.e. code that calls .exec() twice,
  103. // > etc. That's all handled elsewhere (where the exec() method is defined.)
  104. //==========================================================================================
  105. // ██╗ ██╗ █████╗ ███╗ ██╗██████╗ ██╗ ███████╗
  106. // ██║ ██║██╔══██╗████╗ ██║██╔══██╗██║ ██╔════╝
  107. // ███████║███████║██╔██╗ ██║██║ ██║██║ █████╗
  108. // ██╔══██║██╔══██║██║╚██╗██║██║ ██║██║ ██╔══╝
  109. // ██║ ██║██║ ██║██║ ╚████║██████╔╝███████╗███████╗
  110. // ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═════╝ ╚══════╝╚══════╝
  111. //
  112. // ███████╗██╗ ██╗██████╗ ██╗ ██╗ ██████╗██╗████████╗ ██████╗██████╗
  113. // ██╔════╝╚██╗██╔╝██╔══██╗██║ ██║██╔════╝██║╚══██╔══╝ ██╔════╝██╔══██╗
  114. // █████╗ ╚███╔╝ ██████╔╝██║ ██║██║ ██║ ██║ ██║ ██████╔╝
  115. // ██╔══╝ ██╔██╗ ██╔═══╝ ██║ ██║██║ ██║ ██║ ██║ ██╔══██╗
  116. // ███████╗██╔╝ ██╗██║ ███████╗██║╚██████╗██║ ██║ ╚██████╗██████╔╝
  117. // ╚══════╝╚═╝ ╚═╝╚═╝ ╚══════╝╚═╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝╚═════╝
  118. //
  119. // ╦╔═╗ ┌─┐┬─┐┌─┐┬ ┬┬┌┬┐┌─┐┌┬┐
  120. // ║╠╣ ├─┘├┬┘│ │└┐┌┘│ ││├┤ ││
  121. // ╩╚ ┴ ┴└─└─┘ └┘ ┴─┴┘└─┘─┴┘
  122. // If explicitCb provided, run the handleExec logic, then call the explicit callback.
  123. //
  124. // > All of the additional checks from below (e.g. try/catch) are NOT performed
  125. // > in the situation where an explicit callback was provided. This is to allow
  126. // > for userland code to squeeze better performance out of particular method calls
  127. // > by simply passing through the callback directly.
  128. // > (As a bonus, it also avoids duplicating the code below in this file.)
  129. if (explicitCbMaybe) {
  130. handleExec(explicitCbMaybe);
  131. // ██████╗ ███████╗████████╗██╗ ██╗██████╗ ███╗ ██╗
  132. // ██╔══██╗██╔════╝╚══██╔══╝██║ ██║██╔══██╗████╗ ██║
  133. // ██████╔╝█████╗ ██║ ██║ ██║██████╔╝██╔██╗ ██║
  134. // ██╔══██╗██╔══╝ ██║ ██║ ██║██╔══██╗██║╚██╗██║
  135. // ██║ ██║███████╗ ██║ ╚██████╔╝██║ ██║██║ ╚████║
  136. // ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝
  137. //
  138. // ██╗ ██╗███╗ ██╗██████╗ ███████╗███████╗██╗███╗ ██╗███████╗██████╗
  139. // ██║ ██║████╗ ██║██╔══██╗██╔════╝██╔════╝██║████╗ ██║██╔════╝██╔══██╗
  140. // ██║ ██║██╔██╗ ██║██║ ██║█████╗ █████╗ ██║██╔██╗ ██║█████╗ ██║ ██║
  141. // ██║ ██║██║╚██╗██║██║ ██║██╔══╝ ██╔══╝ ██║██║╚██╗██║██╔══╝ ██║ ██║
  142. // ╚██████╔╝██║ ╚████║██████╔╝███████╗██║ ██║██║ ╚████║███████╗██████╔╝
  143. // ╚═════╝ ╚═╝ ╚═══╝╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚═════╝
  144. //
  145. return;
  146. }//-•
  147. // Otherwise, no explicit callback was provided- so we'll build & return a Deferred...
  148. // ██████╗ ████████╗██╗ ██╗███████╗██████╗ ██╗ ██╗██╗███████╗███████╗
  149. // ██╔═══██╗╚══██╔══╝██║ ██║██╔════╝██╔══██╗██║ ██║██║██╔════╝██╔════╝██╗
  150. // ██║ ██║ ██║ ███████║█████╗ ██████╔╝██║ █╗ ██║██║███████╗█████╗ ╚═╝
  151. // ██║ ██║ ██║ ██╔══██║██╔══╝ ██╔══██╗██║███╗██║██║╚════██║██╔══╝ ██╗
  152. // ╚██████╔╝ ██║ ██║ ██║███████╗██║ ██║╚███╔███╔╝██║███████║███████╗╚═╝
  153. // ╚═════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝ ╚═╝╚══════╝╚══════╝
  154. //
  155. // ██████╗ ██╗ ██╗██╗██╗ ██████╗
  156. // ██╔══██╗██║ ██║██║██║ ██╔══██╗
  157. // ██████╔╝██║ ██║██║██║ ██║ ██║
  158. // ██╔══██╗██║ ██║██║██║ ██║ ██║
  159. // ██████╔╝╚██████╔╝██║███████╗██████╔╝
  160. // ╚═════╝ ╚═════╝ ╚═╝╚══════╝╚═════╝
  161. //
  162. // ██████╗ ███████╗███████╗███████╗██████╗ ██████╗ ███████╗██████╗
  163. // ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗██╔══██╗██╔════╝██╔══██╗
  164. // ██║ ██║█████╗ █████╗ █████╗ ██████╔╝██████╔╝█████╗ ██║ ██║
  165. // ██║ ██║██╔══╝ ██╔══╝ ██╔══╝ ██╔══██╗██╔══██╗██╔══╝ ██║ ██║
  166. // ██████╔╝███████╗██║ ███████╗██║ ██║██║ ██║███████╗██████╔╝
  167. // ╚═════╝ ╚══════╝╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═════╝
  168. //
  169. // Build deferred object.
  170. //
  171. // > For more info & benchmarks, see:
  172. // > https://github.com/mikermcneil/parley/commit/5996651c4b15c7850b5eb2e4dc038e8202414553#commitcomment-20256030
  173. // >
  174. // > And also `baseline.benchmark.js` in this repo.
  175. // >
  176. // > But then also see:
  177. // > https://github.com/mikermcneil/parley/commit/023dc9396bdfcd02290624ca23cb2d005037f398
  178. // >
  179. // > (Basically, it keeps going back and forth between this and closures, but after a lot
  180. // > of experimentation, the prototypal approach seems better for overall performance.)
  181. var π = new Deferred(handleExec);
  182. // If appropriate, start the 15 second .exec() countdown.
  183. var EXEC_COUNTDOWN_IN_SECONDS = 15;
  184. if (IS_DEBUG_OR_NON_PRODUCTION_ENV) {
  185. π._execCountdown = setTimeout(function(){
  186. // IWMIH, it means that this Deferred hasn't begun executing,
  187. // even after 15 seconds. This deserves a warning log.
  188. // > See https://trello.com/c/7QnQZ6aC for more background.
  189. console.warn(
  190. '- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n'+
  191. 'WARNING: A function that was initially called over '+EXEC_COUNTDOWN_IN_SECONDS+' seconds\n'+
  192. 'ago has still not actually been executed. Any chance the\n'+
  193. 'source code is missing an "await"?\n'+
  194. '\n'+
  195. (π._omen?(
  196. 'To assist you in hunting this down, here is a stack trace:\n'+
  197. '```\n'+
  198. flaverr.getBareTrace(π._omen)+'\n'+
  199. '```\n'+
  200. '\n'
  201. ):'')+
  202. // 'Please double-check this code is not missing an "await".\n'+
  203. // '\n'+
  204. ' [?] For more help, visit https://sailsjs.com/support\n'+
  205. '- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -'
  206. );
  207. }, EXEC_COUNTDOWN_IN_SECONDS*1000);
  208. }//fi
  209. // ┌─┐┌┬┐┌┬┐┌─┐┌─┐┬ ┬ ╔═╗╦ ╦╔═╗╔╦╗╔═╗╔╦╗ ╔╦╗╔═╗╔╦╗╦ ╦╔═╗╔╦╗╔═╗
  210. // ├─┤ │ │ ├─┤│ ├─┤ ║ ║ ║╚═╗ ║ ║ ║║║║ ║║║║╣ ║ ╠═╣║ ║ ║║╚═╗
  211. // ┴ ┴ ┴ ┴ ┴ ┴└─┘┴ ┴ ╚═╝╚═╝╚═╝ ╩ ╚═╝╩ ╩ ╩ ╩╚═╝ ╩ ╩ ╩╚═╝═╩╝╚═╝
  212. // ┌─ ┬┌─┐ ┬─┐┌─┐┬ ┌─┐┬ ┬┌─┐┌┐┌┌┬┐ ─┐
  213. // │─── │├┤ ├┬┘├┤ │ ├┤ └┐┌┘├─┤│││ │ ───│
  214. // └─ ┴└ ┴└─└─┘┴─┘└─┘ └┘ ┴ ┴┘└┘ ┴ ─┘
  215. // If a dictionary of `customMethods` were provided, attach them dynamically.
  216. if (customMethods) {
  217. // Even with no contents, using an _.each() loop here would actually hurt the performance
  218. // of the "just_build" benchmark by 93% (~13x as slow). Granted, it was very fast to
  219. // begin with... but compare with this `for` loop which, with no contents, only hurts
  220. // the same benchmark's performance by 25% ~(1.3x as slow).
  221. var methodFn;
  222. for (var methodName in customMethods) {
  223. // We explicitly prevent overriding:
  224. if (
  225. // • built-in methods:
  226. methodName === 'exec' ||
  227. methodName === 'then' ||
  228. methodName === 'catch' ||
  229. methodName === 'toPromise' ||
  230. methodName === 'intercept' ||
  231. methodName === 'tolerate' ||
  232. // (Note that we explicitly omit `.log()`, `.now()`, and `.timeout()`
  233. // so that they may be potentially overridden.)
  234. // • other special, private properties:
  235. methodName === '_execCountdown' ||
  236. methodName === '_hasBegunExecuting' ||
  237. methodName === '_hasFinishedExecuting' ||
  238. methodName === '_hasStartedButNotFinishedAfterExecLC' ||
  239. methodName === '_hasAlreadyWaitedAtLeastOneTick' ||
  240. methodName === '_skipImplSpinlockWarning' ||
  241. methodName === '_hasTimedOut' ||
  242. methodName === '_handleExec' ||
  243. methodName === '_promise' ||
  244. methodName === '_timeout' ||
  245. methodName === '_omen' ||
  246. methodName === '_userlandAfterExecLCs' ||
  247. methodName === '_finalAfterExecLC' ||
  248. // • the standard JavaScript object flora:
  249. methodName === '__defineGetter__' ||
  250. methodName === '__defineSetter__' ||
  251. methodName === '__lookupGetter__' ||
  252. methodName === '__lookupSetter__' ||
  253. methodName === '__proto__' ||
  254. methodName === 'constructor' ||
  255. methodName === 'hasOwnProperty' ||
  256. methodName === 'isPrototypeOf' ||
  257. methodName === 'propertyIsEnumerable' ||
  258. methodName === 'toLocaleString' ||
  259. methodName === 'toString' ||
  260. methodName === 'valueOf' ||
  261. // • and things that are just a really bad idea:
  262. // (or at the very least, which shouldn't be defined this way)
  263. methodName === 'prototype' ||
  264. methodName === 'toJSON' ||
  265. methodName === 'inspect'
  266. ) {
  267. throw new Error('Cannot define custom method (`.'+methodName+'()`) because `'+methodName+'` is a reserved/built-in property.');
  268. }
  269. methodFn = customMethods[methodName];
  270. π[methodName] = methodFn;
  271. }//</for>
  272. }//>-
  273. // ┌─┐┌┬┐┌┬┐┌─┐┌─┐┬ ┬ ╔╦╗╦╔╦╗╔═╗╔═╗╦ ╦╔╦╗
  274. // ├─┤ │ │ ├─┤│ ├─┤ ║ ║║║║║╣ ║ ║║ ║ ║
  275. // ┴ ┴ ┴ ┴ ┴ ┴└─┘┴ ┴ ╩ ╩╩ ╩╚═╝╚═╝╚═╝ ╩
  276. // ┌─ ┬┌─┐ ┬─┐┌─┐┬ ┌─┐┬ ┬┌─┐┌┐┌┌┬┐ ─┐
  277. // │─── │├┤ ├┬┘├┤ │ ├┤ └┐┌┘├─┤│││ │ ───│
  278. // └─ ┴└ ┴└─└─┘┴─┘└─┘ └┘ ┴ ┴┘└┘ ┴ ─┘
  279. if (timeout) {
  280. if (!_.isNumber(timeout)) { throw new Error('Consistency violation: If provided, `timeout` argument to parley should be a number (i.e. max # of milliseconds to wait before giving up). But instead, got: '+util.inspect(timeout, {depth:2})+''); }
  281. π._timeout = timeout;
  282. }
  283. // ┌─┐┌┬┐┌┬┐┌─┐┌─┐┬ ┬ ╔═╗╔╦╗╔═╗╔╗╔
  284. // ├─┤ │ │ ├─┤│ ├─┤ ║ ║║║║║╣ ║║║
  285. // ┴ ┴ ┴ ┴ ┴ ┴└─┘┴ ┴ ╚═╝╩ ╩╚═╝╝╚╝
  286. // ┌─ ┬┌─┐ ┬─┐┌─┐┬ ┌─┐┬ ┬┌─┐┌┐┌┌┬┐ ─┐
  287. // │─── │├┤ ├┬┘├┤ │ ├┤ └┐┌┘├─┤│││ │ ───│
  288. // └─ ┴└ ┴└─└─┘┴─┘└─┘ └┘ ┴ ┴┘└┘ ┴ ─┘
  289. if (omen) {
  290. if (!_.isError(omen)) { throw new Error('Consistency violation: If provided, `omen` argument to parley should be a pre-existing omen (i.e. an Error instance). But instead, got: '+util.inspect(omen, {depth:2})+''); }
  291. π._omen = omen;
  292. }
  293. // ┌─┐┌┬┐┌┬┐┌─┐┌─┐┬ ┬ ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔═╗╔╦╗╔═╗╔═╗╔╦╗╔═╗╦═╗╔═╗═╗ ╦╔═╗╔═╗
  294. // ├─┤ │ │ ├─┤│ ├─┤ ║║║║ ║ ║╣ ╠╦╝║ ║╣ ╠═╝ ║ ╠═╣╠╣ ║ ║╣ ╠╦╝║╣ ╔╩╦╝║╣ ║
  295. // ┴ ┴ ┴ ┴ ┴ ┴└─┘┴ ┴ ╩╝╚╝ ╩ ╚═╝╩╚═╚═╝╚═╝╩ ╩ ╩ ╩╚ ╩ ╚═╝╩╚═╚═╝╩ ╚═╚═╝╚═╝
  296. // ┌─ ┬┌─┐ ┬─┐┌─┐┬ ┌─┐┬ ┬┌─┐┌┐┌┌┬┐ ─┐
  297. // │─── │├┤ ├┬┘├┤ │ ├┤ └┐┌┘├─┤│││ │ ───│
  298. // └─ ┴└ ┴└─└─┘┴─┘└─┘ └┘ ┴ ┴┘└┘ ┴ ─┘
  299. if (finalAfterExecLC) {
  300. if (!_.isFunction(finalAfterExecLC)) { throw new Error('Consistency violation: If provided, `finalAfterExecLC` argument to parley should be a handler function (see parley README for more information or visit https://sailsjs.com/support for help). But instead of that function, got: '+util.inspect(finalAfterExecLC, {depth:5})+''); }
  301. π._finalAfterExecLC = finalAfterExecLC;
  302. }
  303. // ██████╗ ███████╗████████╗██╗ ██╗██████╗ ███╗ ██╗
  304. // ██╔══██╗██╔════╝╚══██╔══╝██║ ██║██╔══██╗████╗ ██║
  305. // ██████╔╝█████╗ ██║ ██║ ██║██████╔╝██╔██╗ ██║
  306. // ██╔══██╗██╔══╝ ██║ ██║ ██║██╔══██╗██║╚██╗██║
  307. // ██║ ██║███████╗ ██║ ╚██████╔╝██║ ██║██║ ╚████║
  308. // ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝
  309. //
  310. // ██████╗ ███████╗███████╗███████╗██████╗ ██████╗ ███████╗██████╗
  311. // ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗██╔══██╗██╔════╝██╔══██╗
  312. // ██║ ██║█████╗ █████╗ █████╗ ██████╔╝██████╔╝█████╗ ██║ ██║
  313. // ██║ ██║██╔══╝ ██╔══╝ ██╔══╝ ██╔══██╗██╔══██╗██╔══╝ ██║ ██║
  314. // ██████╔╝███████╗██║ ███████╗██║ ██║██║ ██║███████╗██████╔╝
  315. // ╚═════╝ ╚══════╝╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═════╝
  316. //
  317. // Return deferred object
  318. return π;
  319. };