Deferred.js 60 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356
  1. /**
  2. * Module dependencies
  3. */
  4. var util = require('util');
  5. var _ = require('@sailshq/lodash');
  6. var bluebird = require('bluebird');
  7. var flaverr = require('flaverr');
  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. * Deferred (constructor)
  15. *
  16. * The Deferred constructor is exported by this module (see bottom of file).
  17. *
  18. * ```
  19. * var deferred = new Deferred(function(done){
  20. * // ...
  21. * return done();
  22. * });
  23. * ```
  24. *
  25. * > Why use a constructor instead of just creating this object dynamically?
  26. * > This is purely for performance, since this is a rather hot code path.
  27. * > For details, see "OBSERVATIONS" in `tests/baseline.benchmark.js`.
  28. *
  29. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  30. * @param {Function} handleExec [your function that should be run]
  31. * @param {Function} done [Node-style callback that your function should invoke when finished]
  32. * @param {Error?} err
  33. * @param {Ref?} resultMaybe
  34. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  35. * @constructs {Deferred}
  36. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  37. * @api private
  38. * PLEASE DO NOT REQUIRE AND USE THIS CONSTRUCTOR EXCEPT WITHIN THIS PACKAGE!
  39. * Instead, use `require('parley')` with the documented usage. If you're
  40. * trying to do this because you want to do an instanceof check, e.g. in
  41. * a test, consider checking something like `_.isFunction(foo.exec)` and/or
  42. * `foo.constructor.name === 'Deferred'`.
  43. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  44. */
  45. function Deferred(handleExec){
  46. // assert(_.isFunction(handleExec), 'Should always provide a function as the 1st argument when constructing a `Deferred`');
  47. // assert.strictEqual(arguments.length, 1, 'Should always provide exactly 1 argument when constructing a `Deferred`');
  48. // Attach the provided `handleExec`function so it can be called from inside the
  49. // `.exec()` instance method below.
  50. this._handleExec = handleExec;
  51. // Notes about our instance variables:
  52. //
  53. // • We'll track the provided `handleExec` function as `this._handleExec`,
  54. // so that we have a way to call it when the time comes. (See above.)
  55. //
  56. // • We'll use `this._hasBegunExecuting` below to track whether we have
  57. // begun executing this Deferred yet. This is mainly for spinlocks.
  58. //
  59. // • We'll use `this._execCountdown` for our .exec() warning countdown.
  60. // (This is the logic that helps you remember to type `await`.)
  61. //
  62. // • We'll use another instance variable below, `this._hasFinishedExecuting`,
  63. // to track whether this Deferred has _finished_ yet. This is mainly to
  64. // improve error & warning messages.
  65. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  66. // FUTURE: Investigate the performance impact of using Object.defineProperty()
  67. // for this and all other built-in instance variables (e.g. the spinlock-related
  68. // flags.) Also maybe for things like `toString()` and `inspect()`.
  69. //
  70. // This is really just as a nicety. The reason it was so slow before was likely
  71. // due to the fact that it got run on every invocation, whereas now (at least in
  72. // some cases, like for `inspect()`) it could be implemented so that it runs exactly
  73. // once per process.
  74. //
  75. // Why bother? Just because it makes Deferred instances nicer to look at.
  76. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  77. }
  78. /**
  79. * .exec()
  80. *
  81. * @param {Function} _cb
  82. * The Node-style callback to invoke when the parley-wrapped implementation is finished.
  83. *
  84. * @param {Function} handleUncaughtException
  85. * If specified, this function will be used as a handler for uncaught exceptions
  86. * thrown from within `_cb`. **But REMEMBER: this will not handle uncaught exceptions
  87. * from any OTHER asynchronous callbacks which might happen to be used within `_cb`.**
  88. * (It's the same sort of function you might pass into `.catch()`.)
  89. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  90. * ^^FUTURE: deprecate, then remove support for this 2nd arg
  91. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  92. *
  93. * ------------------------------------------------------------------------------------------
  94. * Example usage:
  95. *
  96. * ```
  97. * User.create({ username: 'foo' }).exec(function (err, result) {
  98. * if (err) {
  99. * if (err.code === 'E_UNIQUE') { return res.badRequest('Username already in use.'); }
  100. * else { return res.serverError(err); }
  101. * }
  102. *
  103. * return res.ok();
  104. *
  105. * }, res.serverError);
  106. * ```
  107. */
  108. Deferred.prototype.exec = function(_cb, handleUncaughtException){
  109. // Since thar be closure scope below, a hazard for young `this`s, we define `self`.
  110. var self = this;
  111. if (_cb === undefined) {
  112. throw flaverr({
  113. name:
  114. 'UsageError',
  115. message:
  116. 'No callback supplied. Please provide a callback function when calling .exec().\n'+
  117. ' [?] See https://sailsjs.com/support for help.'
  118. }, self._omen);
  119. }//-•
  120. if (!_.isFunction(_cb)) {
  121. if (!_.isArray(_cb) && _.isObject(_cb)) {
  122. throw flaverr({
  123. name:
  124. 'UsageError',
  125. message:
  126. 'Sorry, `.exec()` doesn\'t know how to handle {...} callbacks.\n'+
  127. 'Please provide a callback function when calling .exec().\n'+
  128. '| If you passed in {...} on purpose as a "switchback" (dictionary of callbacks),\n'+
  129. '| then try calling .switch() intead of .exec().\n'+
  130. ' [?] See https://sailsjs.com/support for more help.'
  131. }, self._omen);
  132. }
  133. else {
  134. throw flaverr({
  135. name:
  136. 'UsageError',
  137. message:
  138. 'Sorry, `.exec()` doesn\'t know how to handle a callback like that:\n'+
  139. util.inspect(_cb, {depth: 1})+'\n'+
  140. '\n'+
  141. 'Instead, please provide a callback function when calling .exec().\n'+
  142. ' [?] See https://sailsjs.com/support for help.'
  143. }, self._omen);
  144. }
  145. }//-•
  146. // Check usage of 2nd argument to .exec().
  147. if (handleUncaughtException !== undefined) {
  148. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  149. // FUTURE: Consider adding a deprecation notice to this behavior, and then remove support
  150. // altogether. (This isn't really as necessary anymore now that ES8 async/await is widely
  151. // available on the server, and should be available in the browser... soonish?)
  152. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  153. if (!_.isFunction(handleUncaughtException)) {
  154. throw flaverr({
  155. message:
  156. 'Sorry, `.exec()` doesn\'t know how to handle an uncaught exception handler like that:\n'+
  157. util.inspect(handleUncaughtException, {depth: 1})+'\n'+
  158. 'If provided, the 2nd argument to .exec() should be a function like `function(err){...}`\n'+
  159. '(This function will be used as a failsafe in case the callback throws an uncaught error.)\n'+
  160. ' [?] See https://sailsjs.com/support for help.'
  161. }, self._omen);
  162. }//•
  163. // Impose arbitrary restriction against an unsupported edge case.
  164. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  165. // FUTURE: maybe remove this restriction. But also... might not even be relevant, since
  166. // we'll probably get rid of support for this usage (see above)
  167. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  168. if (self._finalAfterExecLC){
  169. throw new Error('Consistency violation: Currently, the 2nd argument to .exec() may not be used, since this Deferred was built with a custom `finalAfterExecLC` handler. Please avoid using the 2nd argument to .exec() and use ES8 async/await instead, if possible.');
  170. }//•
  171. }//fi
  172. // Userland spinlock
  173. if (self._hasBegunExecuting) {
  174. console.warn(
  175. '\n'+
  176. 'That\'s odd... It looks like this Deferred '+
  177. 'has already '+(self._hasTimedOut?'timed out':self._hasFinishedExecuting?'finished executing':'begun executing')+'.\n'+
  178. 'But attempting to execute a Deferred more than once tends to cause\n'+
  179. 'unexpected race conditions and other bugs! So to be safe, rather than\n'+
  180. 'executing it twice, the additional attempt was ignored automatically, and\n'+
  181. 'this warning was logged instead. [?] See https://sailsjs.com/support for help.\n'+
  182. (self._omen?(
  183. '\n'+
  184. 'Stack trace:\n'+
  185. '```\n'+
  186. flaverr.getBareTrace(self._omen)+'\n'+
  187. '```\n'
  188. ):'')
  189. );
  190. return;
  191. }//-•
  192. self._hasBegunExecuting = true;
  193. // Clear countdown, if appropriate.
  194. if (IS_DEBUG_OR_NON_PRODUCTION_ENV) {
  195. clearTimeout(this._execCountdown);
  196. }
  197. // Before proceeding to execute the function, set up a `setTimeout` that will fire
  198. // when the runtime duration exceeds the configured timeout.
  199. // > If configured timeout is falsey or <0, then we ignore it.
  200. // > Also note that we include a worst-case-scenario spinlock here (but it should never fire!)
  201. var timeoutAlarm;
  202. if (self._timeout && self._timeout > 0) {
  203. timeoutAlarm = setTimeout(function(){
  204. if (self._hasFinishedExecuting) {
  205. console.warn(
  206. 'WARNING: Consistency violation: Trying to trigger timeout, but execution has\n'+
  207. 'already finished! This should not be possible, and the fact that you\'re seeing\n'+
  208. 'this message indicates that there is probably a bug somewhere in the tools -- or\n'+
  209. 'possibly that this Deferred instance has been mutated by userland code.\n'+
  210. 'Please report this at https://sailsjs.com/bugs or https://sailsjs.com/support.\n'+
  211. '(silently ignoring it this time...)\n'+
  212. (self._omen?(
  213. '\n'+
  214. 'Stack trace:\n'+
  215. '```\n'+
  216. flaverr.getBareTrace(self._omen)+'\n'+
  217. '```\n'
  218. ):'')
  219. );
  220. return;
  221. }//•
  222. if (self._hasTimedOut) {
  223. console.warn(
  224. 'WARNING: Consistency violation: Trying to trigger timeout again after it has already\n'+
  225. 'been triggered! This should not be possible, and the fact that you\'re seeing\n'+
  226. 'this message indicates that there is probably a bug somewhere in the tools -- or\n'+
  227. 'possibly that this Deferred instance has been mutated by userland code.\n'+
  228. 'Please report this at https://sailsjs.com/bugs or https://sailsjs.com/support.\n'+
  229. '(silently ignoring it this time...)\n'+
  230. (self._omen?(
  231. '\n'+
  232. 'Stack trace:\n'+
  233. '```\n'+
  234. flaverr.getBareTrace(self._omen)+'\n'+
  235. '```\n'
  236. ):'')
  237. );
  238. return;
  239. }//•
  240. self._hasTimedOut = true;
  241. var err = flaverr({
  242. name:
  243. 'TimeoutError',
  244. traceRef:
  245. self,
  246. message:
  247. 'Took too long to finish executing (timeout of '+self._timeout+'ms exceeded.)\n'+
  248. 'There is probably an issue in the implementation (might have forgotten to call `exits.success()`, etc.)\n'+
  249. 'If you are the implementor of the relevant logic, and you\'re sure there are no problems, then you\'ll\n'+
  250. 'want to (re)configure the timeout (maximum number of milliseconds to wait for the asynchronous logic to\n'+
  251. 'finish). Otherwise, you can set the timeout to 0 to disable it.\n'+
  252. ' [?] See https://sailsjs.com/support for help.',
  253. }, self._omen);
  254. return proceedToInterceptsAndChecks(err, undefined, undefined, self, _cb, handleUncaughtException);
  255. }, self._timeout);// _∏_ (invoking `setTimeout()`)
  256. }//>-
  257. // Trigger the executioner function.
  258. // > Note that we always wrap the executioner in a `try` block to prevent common issues from
  259. // > uncaught exceptions, at least within the tick.
  260. try {
  261. self._handleExec(function( /*…*/ ){
  262. // > Note that we don't use .slice() on the `arguments` keyword -- this is for perf.
  263. // > (see https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#what-is-safe-arguments-usage)
  264. var errCbArg;
  265. var resultCbArg;
  266. var extraCbArgs;
  267. if (arguments.length > 2) {
  268. errCbArg = arguments[0];
  269. resultCbArg = arguments[1];
  270. extraCbArgs = Array.prototype.slice.call(arguments, 2);
  271. } else if (arguments.length > 1) {
  272. errCbArg = arguments[0];
  273. resultCbArg = arguments[1];
  274. } else if (arguments.length > 0) {
  275. errCbArg = arguments[0];
  276. }
  277. proceedToAfterExecSpinlocks(errCbArg, resultCbArg, extraCbArgs, self, _cb, handleUncaughtException, timeoutAlarm);
  278. });//_∏_ </self._handleExec>
  279. } catch (e) {// Handle errors thrown synchronously by the `_handleExec` implementation:
  280. // Check to make sure this error isn't a special "escape hatch" from
  281. // the edge case where an error was thrown from within the userland callback
  282. // provided to .exec() -- specifically in the case where the handleExec logic
  283. // is synchronous (i.e. non-blocking- triggering its `done` within 1 tick.)
  284. if (e && e.name === 'Envelope' && e.code === 'E_ESCAPE_HATCH' && e.traceRef === self) {
  285. throw e.raw;
  286. }//•
  287. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  288. // NOTE: The following code is ALMOST exactly the same as the code above.
  289. // It's duplicated in place rather than extrapolating purely for performance,
  290. // and since the error messages vary a bit:
  291. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  292. if (self._hasFinishedExecuting) {
  293. console.warn(
  294. '- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n'+
  295. 'WARNING: Something seems to be wrong with this function.\n'+
  296. 'It threw an unhandled error during the first tick of its\n'+
  297. 'execution... but before doing so, it somehow managed to\n'+
  298. 'have already resolved/rejected once.\n'+
  299. '(silently ignoring this...)\n'+
  300. (self._omen?(
  301. '\n'+
  302. 'To assist you in hunting this down, here is a stack trace:\n'+
  303. '```\n'+
  304. flaverr.getBareTrace(self._omen)+'\n'+
  305. '```\n'+
  306. '\n'
  307. ):'')+
  308. ' [?] For more help, visit https://sailsjs.com/support\n'+
  309. '- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -'
  310. );
  311. return;
  312. }//-•
  313. if (self._hasTimedOut) {
  314. console.warn(
  315. 'WARNING: Consistency violation: This function threw an unhandled error during the\n'+
  316. 'first tick of its execution... but before doing so, it somehow managed to\n'+
  317. 'have already triggered the timeout. This should not be possible, and the fact that\n'+
  318. 'you\'re seeing this message indicates that there is probably a bug somewhere in the\n'+
  319. 'tools -- or possibly that this Deferred instance has been mutated by userland code.\n'+
  320. 'Please report this at https://sailsjs.com/bugs or https://sailsjs.com/support.\n'+
  321. '(silently ignoring it this time...)\n'+
  322. (self._omen?(
  323. '\n'+
  324. 'To assist you in hunting this down, here is a stack trace:\n'+
  325. '```\n'+
  326. flaverr.getBareTrace(self._omen)+'\n'+
  327. '```\n'+
  328. '\n'
  329. ):'')
  330. );
  331. self._hasFinishedExecuting = true;
  332. return;
  333. }//-•
  334. if (timeoutAlarm) {
  335. clearTimeout(timeoutAlarm);
  336. }//>-
  337. // Ensure we end up with an Error instance.
  338. var err = flaverr.parseOrBuildError(e, self._omen);
  339. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  340. // ^ FUTURE: Better error message for non-Errors?
  341. // (See impl of parseOrBuildError() in flaverr for more context.)
  342. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  343. self._hasFinishedExecuting = true;
  344. return proceedToInterceptsAndChecks(err, undefined, undefined, self, _cb, handleUncaughtException);
  345. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  346. // FUTURE: Consider using the more detailed explanation for ALL 4 (!!) of the cases
  347. // handled by `.parseOrBuildError()` -- and use omens for them too...even the Error instances!
  348. // > see other "FUTURE" block above to read about the other stuff we'd want to do first--
  349. // > and also note that we'd almost certainly want to leave out the bit about "available
  350. // > keys on `self`" bit (that's really just leftover from debugging)
  351. // ```
  352. // return proceedToInterceptsAndChecks(flaverr({
  353. // message:
  354. // 'Unexpected error was thrown while executing '+
  355. // 'this Deferred:\n'+
  356. // '```\n'+
  357. // flaverr.getBareTrace(self._omen)+'\n'+
  358. // '```\n'+
  359. // 'Also, here are the available keys on `self` at this point:\n'+
  360. // '```\n'+
  361. // _.keys(self)+'\n'+
  362. // '```'
  363. // }, self._omen), undefined, undefined, self, _cb, handleUncaughtException);
  364. // ```
  365. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  366. }//</catch>
  367. // Use `self._hasAlreadyWaitedAtLeastOneTick` to track whether or not this
  368. // Deferred's logic is holistically asynchronous.
  369. if (self._hasFinishedExecuting && !self._hasStartedButNotFinishedAfterExecLC) {
  370. // IWMIH then we've already finished running `handleExec`, and we know
  371. // it must have been composed purely of blocking (i.e. synchronous) logic.
  372. // We also know there isn't an actively-running asynchronous afterExec LC
  373. // gumming up the works.
  374. self._hasAlreadyWaitedAtLeastOneTick = false;
  375. }
  376. else {
  377. // Otherwise, IWMIH we know that the callback hasn't been called yet-- meaning
  378. // that we're likely dealing with some non-blocking (i.e. asynchronous) logic.
  379. // (Or possibly a bug where the callback isn't getting called -_-)
  380. // OR possibly a long-running, asynchronous afterExec LC that hasn't finished
  381. // yet.
  382. self._hasAlreadyWaitedAtLeastOneTick = true;
  383. }
  384. };
  385. /**
  386. * .then()
  387. *
  388. * For usage, see:
  389. * http://bluebirdjs.com/docs/api/then.html
  390. */
  391. Deferred.prototype.then = function (){
  392. var promise = this.toPromise();
  393. // > Note that we don't use .slice() -- this is for perf.
  394. // > (see https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#what-is-safe-arguments-usage)
  395. return promise.then.apply(promise, arguments);
  396. };
  397. /**
  398. * .catch()
  399. *
  400. * For usage, see:
  401. * http://bluebirdjs.com/docs/api/catch.html
  402. */
  403. Deferred.prototype.catch = function (){
  404. var promise = this.toPromise();
  405. return promise.catch.apply(promise, arguments);
  406. // Note: While the following is all very nice, we would need to change `Promise.prototype` to
  407. // have it be universally useful. So to avoid that can of worms, leaving this commented out,
  408. // and the above commented _in_. (See the "FUTURE" note just below here for more thoughts about
  409. // a better solution that would allow for mixing Deferred syntax for error handling with standard
  410. // ES8 async/await)
  411. // ================================================================================================
  412. // Deferred.prototype.catch = function (_handlerOrEligibilityFilter, _handlerMaybe){
  413. // // Start running the logic and get a promise.
  414. // var promise = this.toPromise();
  415. //
  416. // // If first argument is string, then we'll assume that the 2nd argument must be a function,
  417. // // and that the intent was to understand this as:
  418. // // ```
  419. // // .catch({ code: theFirstArgument }, handler);
  420. // // ```
  421. // // > Note that this behavior is an **EXTENSION OF BLUEBIRD!**
  422. // // > Thus it is not supported outside of parley, and it is a deliberate divergence
  423. // // > in order to more closely match the one-step-simpler interface exposed by tools
  424. // // > like `flaverr`.
  425. // if (_.isString(_handlerOrEligibilityFilter)) {
  426. // if (!_handlerMaybe) {
  427. // throw flaverr({
  428. // name:
  429. // 'UsageError',
  430. // message:
  431. // 'Invalid usage of `.catch()`. If the first argument to .catch() is NOT a function,\n'+
  432. // 'then a handler function should always be passed in as the second argument.\n'+
  433. // 'See https://sailsjs.com/support for help.'
  434. // }, this._omen);
  435. // }
  436. // return promise.catch.apply(promise, [{ code: _handlerOrEligibilityFilter }, _handlerMaybe]);
  437. // }
  438. // // Otherwise, if a second argument of some kind was supplied, we'll assume it's
  439. // // our handler function, and that the first argument must be a valid eligibility
  440. // // filter.
  441. // else if (_handlerMaybe) {
  442. // return promise.catch.apply(promise, [_handlerOrEligibilityFilter, _handlerMaybe]);
  443. // }
  444. // // Otherwise, since there's no second argument, there must not be an eligibility filter.
  445. // // So we'll treat the first argument as our handler.
  446. // else {
  447. // return promise.catch.apply(promise, [_handlerOrEligibilityFilter]);
  448. // }
  449. // ================================================================================================
  450. };
  451. /**
  452. * .intercept()
  453. *
  454. * Bind a special after-exec lifecycle callback that is useful for easily catching+rethrowing
  455. * errors, without the risk of unintentionally swallowing unrelated bugs by using other
  456. * mechanisms (e.g. try/catch blocks)
  457. *
  458. * > See `bindUserlandAfterExecLC` utility for details on how this works.
  459. *
  460. * Also note that .intercept() gets one extra special shorthand notation:
  461. * If desired, you can specific a non-empty string instead of a function as your "specific handler".
  462. * In this case, a handler will be constructed automatically: simply a function that returns
  463. * the specified string. This is particularly useful for situations such as intercepting
  464. * an exception and throwing a different special exit signal for an enclosing action (for example,
  465. * within one of Sails's helpers, shell scripts, or actions2-style actions, or in the
  466. * implementation of a machinepack.)
  467. */
  468. Deferred.prototype.intercept = function(negotiationRuleOrWildcardHandler, specificHandler, _lcOpts){
  469. if (_.isString(specificHandler)) {
  470. var originalString = specificHandler;
  471. specificHandler = function(){
  472. return originalString;
  473. };//ƒ
  474. }
  475. bindUserlandAfterExecLC('intercept', negotiationRuleOrWildcardHandler, specificHandler, this, _lcOpts);
  476. return this;
  477. };
  478. /**
  479. * .tolerate()
  480. *
  481. * Bind a special after-exec lifecycle callback that is useful for gracefully handling
  482. * particular errors without accidentally swallowing unrelated bugs by using other mechanisms
  483. * (e.g. try/catch blocks).
  484. *
  485. * > See `bindUserlandAfterExecLC` utility for details on how this works.
  486. */
  487. Deferred.prototype.tolerate = function(negotiationRuleOrWildcardHandler, specificHandler, _lcOpts){
  488. bindUserlandAfterExecLC('tolerate', negotiationRuleOrWildcardHandler, specificHandler, this, _lcOpts);
  489. return this;
  490. };
  491. /**
  492. * .toPromise()
  493. *
  494. * Begin executing this Deferred and return a promise.
  495. *
  496. * > See also:
  497. * > http://bluebirdjs.com/docs/api/promisify.html
  498. *
  499. * @returns {Promise}
  500. */
  501. Deferred.prototype.toPromise = function (){
  502. // Use cached promise, if one has already been created.
  503. //
  504. // > This prevents extraneous invocation in `.then()` chains.
  505. // > (& potentially improves perf of `.catch()`)
  506. if (this._promise) {
  507. return this._promise;
  508. }//-•
  509. // Build a function that, when called, will begin executing the underlying
  510. // logic and also return a promise.
  511. var getPromise = bluebird.promisify(this.exec, { context: this });
  512. // ^^The `{context: this}` thing is equivalent to `bluebird.promisify(this.exec).bind(this)`,
  513. // > more or less. The reason we have to use `.bind()` here is so that `.exec()`
  514. // > gets called w/ the appropriate context. (If we were using closures instead
  515. // > of a prototypal thing, we wouldn't have to do this.)
  516. // Make a promise, and cache it as `this._promise` so that it can be
  517. // reused (e.g. in case there are multiple calls to `.then()` and `.catch()`)
  518. this._promise = getPromise();
  519. return this._promise;
  520. };
  521. /**
  522. * .now()
  523. *
  524. * Execute the Deferred and get an immediate result.
  525. * (Only works for synchronous logic.)
  526. *
  527. * @returns {Ref} result from invoking the `handleExec` function
  528. * @throws {Error} If something goes wrong with the `handleExec` logic
  529. */
  530. Deferred.prototype.now = function () {
  531. var isFinishedExecuting;
  532. var immediateResult;
  533. var immediateError;
  534. this.exec(function (err, result){
  535. isFinishedExecuting = true;
  536. immediateError = err;
  537. immediateResult = result;
  538. });//_∏_
  539. if (!isFinishedExecuting) {
  540. this._skipImplSpinlockWarning = true;
  541. // ^^This indicates that this Deferred should now silently ignore any
  542. // extra attempts to trigger its callbacks- whether that's from within
  543. // the custom implementation or from something more built-in (e.g. the
  544. // timeout) -- i.e. because we've already sent back a proper error,
  545. // and the extra warnings just muddy the waters, so to speak- making
  546. // it harder to tell what the actual problem was.
  547. throw flaverr({
  548. name:
  549. 'UsageError',
  550. code:
  551. 'E_NOT_SYNCHRONOUS',
  552. message:
  553. 'Failed to call this function synchronously with `.now()` because\n'+
  554. 'it is not actually synchronous. Rather than `.now()`, please use '+
  555. '`await` or `.exec()`, `.then()`, etc.',
  556. }, this._omen);
  557. }//•
  558. if (immediateError) {
  559. throw immediateError;
  560. }//•
  561. return immediateResult;
  562. };
  563. /**
  564. * .log()
  565. *
  566. * Log the output from this asynchronous logic, fully-expanded.
  567. * (Uses `util.inspect(..., {depth: null})`.)
  568. *
  569. * Note: This is for debugging / for exploration purposes, especially
  570. * for use on the Node.js/Sails.js REPL or in the browser console.
  571. * If there is an error, it will simply be logged to stderr.
  572. */
  573. Deferred.prototype.log = function (){
  574. if (!IS_DEBUG_OR_NON_PRODUCTION_ENV) {
  575. console.warn('* * * * * * * * * * * * * * * * * * * * * * * * * *');
  576. console.warn('Warning: Production environment detected...');
  577. console.warn('Please reconsider using using .log() in production.');
  578. console.warn(' [?] If you\'re unsure, see https://sailsjs.com/support');
  579. console.warn('* * * * * * * * * * * * * * * * * * * * * * * * * *');
  580. }
  581. else {
  582. console.log('Running with `.log()`...');
  583. }
  584. this.exec(function(err, result) {
  585. if (err) {
  586. console.error();
  587. console.error('- - - - - - - - - - - - - - - - - - - - - - - -');
  588. console.error('An error occurred:');
  589. console.error();
  590. console.error(err);
  591. console.error('- - - - - - - - - - - - - - - - - - - - - - - -');
  592. console.error();
  593. return;
  594. }//-•
  595. console.log();
  596. if (result === undefined) {
  597. console.log('- - - - - - - - - - - - - - - - - - - - - - - -');
  598. console.log('Finished successfully.');
  599. console.log();
  600. console.log('(There was no result.)');
  601. console.log('- - - - - - - - - - - - - - - - - - - - - - - -');
  602. }
  603. else {
  604. console.log('- - - - - - - - - - - - - - - - - - - - - - - -');
  605. console.log('Finished successfully.');
  606. console.log();
  607. console.log('Result:');
  608. console.log();
  609. console.log(util.inspect(result, {depth: null}));
  610. console.log('- - - - - - - - - - - - - - - - - - - - - - - -');
  611. }
  612. console.log();
  613. });
  614. };
  615. /**
  616. * .timeout()
  617. *
  618. * Set a timeout for this invocation (i.e. from userland).
  619. *
  620. * > Note: This works by reusing/overriding the implementor-land `_timeout` property.
  621. *
  622. * @param {Number} ms [number of milliseconds to wait for execution to finish before considering this timed out]
  623. * @throws {Error} If already begun executing
  624. */
  625. Deferred.prototype.timeout = function (ms){
  626. if (!_.isNumber(ms)) {
  627. throw flaverr({
  628. name:
  629. 'UsageError',
  630. message:
  631. 'Invalid usage for `.timeout()`. Please provide a number of milliseconds\n'+
  632. 'as the first argument, or use 0 to eliminate the timeout for this invocation.'
  633. }, this._omen);
  634. }
  635. if (this._hasBegunExecuting) {
  636. throw flaverr({
  637. name:
  638. 'UsageError',
  639. message:
  640. 'Could not attach max milliseconds with `.timeout()` because this invocation\n'+
  641. 'has already '+(this._hasTimedOut?'timed out':this._hasFinishedExecuting?'finished executing':'begun executing')+'.'
  642. }, this._omen);
  643. }
  644. this._timeout = ms;
  645. return this;
  646. };
  647. // Attach `inspect`, `toString`, and `toJSON` functions
  648. // (This is mainly to hide the `_omen` property, which is pretty scary-looking)
  649. Deferred.prototype.toJSON = function (){ return null; };
  650. Deferred.prototype.toString = function (){ return '[Deferred]'; };
  651. Deferred.prototype.inspect = function (){ return '[Deferred]'; };
  652. /**
  653. * .getInvocationInfo()
  654. *
  655. * Return information about this Deferred.
  656. *
  657. * > Note: This function is designed for use in higher-level tools.
  658. * > It provides a public API for accessing Deferred state such as `_timeout`.
  659. *
  660. * @returns {Dictionary}
  661. * @property {Number?} timeout
  662. * @property {Array?} @of {Dictionary} interruptions
  663. * @property {String} type
  664. * @property {String|Dictionary|Array} rule
  665. * @property {String|Function?} handler
  666. * @property {Boolean} isThenable
  667. */
  668. Deferred.prototype.getInvocationInfo = function (){
  669. return {
  670. timeout: this._timeout,
  671. interruptions: this._userlandAfterExecLCs
  672. };
  673. };
  674. // Finally, export the Deferred constructor.
  675. // (we could have done this earlier-- we just do it down here for consistency)
  676. module.exports = Deferred;
  677. //////////////////////////////////////////////////////////////////////////////////////////
  678. // ██████╗ ██████╗ ████████╗██╗███╗ ███╗██╗███████╗ █████╗ ████████╗██╗ ██████╗ ███╗ ██╗███████╗
  679. // ██╔═══██╗██╔══██╗╚══██╔══╝██║████╗ ████║██║╚══███╔╝██╔══██╗╚══██╔══╝██║██╔═══██╗████╗ ██║██╔════╝██╗
  680. // ██║ ██║██████╔╝ ██║ ██║██╔████╔██║██║ ███╔╝ ███████║ ██║ ██║██║ ██║██╔██╗ ██║███████╗╚═╝
  681. // ██║ ██║██╔═══╝ ██║ ██║██║╚██╔╝██║██║ ███╔╝ ██╔══██║ ██║ ██║██║ ██║██║╚██╗██║╚════██║██╗
  682. // ╚██████╔╝██║ ██║ ██║██║ ╚═╝ ██║██║███████╗██║ ██║ ██║ ██║╚██████╔╝██║ ╚████║███████║╚═╝
  683. // ╚═════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝
  684. //
  685. // Our callback (`_cb`) is intercepted by a couple of other functions. This is
  686. // just a slightly-more-efficient alternative to a series of self-calling functions.
  687. // We only do this to afford better performance in the general case.
  688. //
  689. // > (Normally, this sort of micro-optimization wouldn't matter, but this is an extradordinarily
  690. // > hot code path. Note that if we can prove self-calling functions are just as good, or even
  691. // > good enough, it would be preferable to use them instead (not only for consistency, but
  692. // > certainly for clarity as well).)
  693. //
  694. // To begin with, no matter what, intercept `_cb` by wrapping it in another function
  695. // (a new one that we'll call `cb`) which adds some additional checks.
  696. //////////////////////////////////////////////////////////////////////////////////////////
  697. /**
  698. * Used exclusively by `Deferred.prototype.exec()`, this function is an optimization.
  699. * It would be much better to use an IIFE instead of defining this function, but we're
  700. * dealing with a very hot code path, so the performance gain is worth it.
  701. * That said, this optimization should never be applied in normal userland code!
  702. */
  703. function proceedToAfterExecSpinlocks (errCbArg, resultCbArg, extraCbArgs, self, _cb, handleUncaughtException, timeoutAlarm) {
  704. // Implementorland spinlock
  705. if (self._hasFinishedExecuting && !self._skipImplSpinlockWarning) {
  706. console.warn(
  707. '- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n'+
  708. 'WARNING: Something seems to be wrong with this function.\n'+
  709. 'It is trying to signal that it has finished AGAIN, after\n'+
  710. 'already resolving/rejecting once.\n'+
  711. '(silently ignoring this...)\n'+
  712. (self._omen?(
  713. '\n'+
  714. 'To assist you in hunting this down, here is a stack trace:\n'+
  715. '```\n'+
  716. flaverr.getBareTrace(self._omen)+'\n'+
  717. '```\n'+
  718. '\n'
  719. ):'')+
  720. ' [?] For more help, visit https://sailsjs.com/support\n'+
  721. '- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -'
  722. );
  723. return;
  724. }//•
  725. // If the deferred has already timed out, then there's no need to warn
  726. // (This was _bound_ to happen beings as how we timed out.)
  727. //
  728. // > Note that we still set a flag to track that this happened. This is to make sure
  729. // > this if/then statement can't possibly be true more than once (because that would
  730. // > definitely still be unexpected-- and really hard to anticipate / debug if it were
  731. // > to happen to you)
  732. if (self._hasTimedOut) {
  733. self._hasFinishedExecuting = true;
  734. return;
  735. }//•
  736. // Clear timeout, if relevant.
  737. if (timeoutAlarm) {
  738. clearTimeout(timeoutAlarm);
  739. }
  740. if (errCbArg) {
  741. // Ensure we're dealing w/ an Error instance.
  742. errCbArg = flaverr.parseOrBuildError(errCbArg, self._omen);
  743. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  744. // ^ FUTURE: Better error message for non-Errors?
  745. // (See impl of parseOrBuildError() in flaverr for more context.)
  746. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  747. }//fi
  748. self._hasFinishedExecuting = true;
  749. return proceedToInterceptsAndChecks(errCbArg, resultCbArg, extraCbArgs, self, _cb, handleUncaughtException);
  750. }//ƒ </proceedToAfterExecSpinlocks>
  751. /**
  752. * Used exclusively by `Deferred.prototype.exec()` and `proceedToAfterExecSpinlocks`, this function is an optimization.
  753. * It would be much better to use an IIFE instead of defining this function, but we're
  754. * dealing with a very hot code path, so the performance gain is worth it.
  755. * That said, this optimization should never be applied in normal userland code!
  756. */
  757. function proceedToInterceptsAndChecks (errCbArg, resultCbArg, extraCbArgs, self, _cb, handleUncaughtException) {
  758. // ┬ ┬┬─┐┌─┐┌─┐ ┌─┐┌─┐┌┬┐┌─┐┌┐┌┌┬┐┬┌─┐┬ ┬ ┬ ┬ ┌─┐┌─┐┌┐┌┌─┐┬ ┬┌─┐┬┌┐┌┌─┐ ┌─┐┬─┐┬─┐┌─┐┬─┐┌─┐
  759. // │││├┬┘├─┤├─┘ ├─┘│ │ │ ├┤ │││ │ │├─┤│ │ └┬┘───│ │ ││││├┤ │ │└─┐│││││ ┬ ├┤ ├┬┘├┬┘│ │├┬┘└─┐
  760. // └┴┘┴└─┴ ┴┴ ┴ └─┘ ┴ └─┘┘└┘ ┴ ┴┴ ┴┴─┘┴─┘┴ └─┘└─┘┘└┘└ └─┘└─┘┴┘└┘└─┘ └─┘┴└─┴└─└─┘┴└─└─┘
  761. // ┌─┐┬─┐┌─┐┌┬┐ ┌─┐┌┬┐┬ ┬┌─┐┬─┐ ┌┬┐┌─┐┌─┐┌─┐┬─┐┬─┐┌─┐┌┬┐ ┬┌┐┌┬ ┬┌─┐┌─┐┌─┐┌┬┐┬┌─┐┌┐┌┌─┐
  762. // ├┤ ├┬┘│ ││││ │ │ │ ├─┤├┤ ├┬┘ ││├┤ ├┤ ├┤ ├┬┘├┬┘├┤ ││ ││││└┐┌┘│ ││ ├─┤ │ ││ ││││└─┐
  763. // └ ┴└─└─┘┴ ┴ └─┘ ┴ ┴ ┴└─┘┴└─ ─┴┘└─┘└ └─┘┴└─┴└─└─┘─┴┘ ┴┘└┘ └┘ └─┘└─┘┴ ┴ ┴ ┴└─┘┘└┘└─┘
  764. // ┬ ┬┬┌┬┐┬ ┬┬┌┐┌ ┌┬┐┬ ┬┌─┐ ┬ ┬┌─┐┌┐┌┌┬┐┬ ┌─┐┌─┐─┐ ┬┌─┐┌─┐ ┌─┐┬ ┬┌┐┌┌─┐┌┬┐┬┌─┐┌┐┌
  765. // ││││ │ ├─┤││││ │ ├─┤├┤ ├─┤├─┤│││ │││ ├┤ ├┤ ┌┴┬┘├┤ │ ├┤ │ │││││ │ ││ ││││
  766. // └┴┘┴ ┴ ┴ ┴┴┘└┘ ┴ ┴ ┴└─┘ ┴ ┴┴ ┴┘└┘─┴┘┴─┘└─┘└─┘┴ └─└─┘└─┘ └ └─┘┘└┘└─┘ ┴ ┴└─┘┘└┘
  767. if (errCbArg) {
  768. var doWrap;
  769. // If we see E_NOT_SYNCHRONOUS, it should ALWAYS be wrapped.
  770. // (The only time it would ever come from THIS Deferred is if we called .now() --
  771. // and the code that checks that is not even part of .exec())
  772. if (_.isObject(errCbArg) && errCbArg.code === 'E_NOT_SYNCHRONOUS') {
  773. doWrap = true;
  774. }
  775. // If we see a TimeoutError from a Deferred **OTHER** than this one,
  776. // then wrap it.
  777. else if (_.isObject(errCbArg) && errCbArg.name === 'TimeoutError' && errCbArg.traceRef !== self) {
  778. doWrap = true;
  779. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  780. // Note: An easy way to test this is to run something like the following in the Node REPL:
  781. // ````
  782. // require('machine')({identity: 'outside', fn: (inputs, exits)=>{ require('machine')({identity: 'inside',timeout: 2000, exits: {notFound:{}}, fn: (inputs, exits)=>{ /*deliberately never exits...*/ }})().exec((err)=>{ if (err){return exits.error(err);} return exits.success(); }); }})()
  783. // ````
  784. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  785. }
  786. // If instructed to do so, perform the wrapping.
  787. if (doWrap) {
  788. errCbArg = flaverr.wrap({
  789. code:
  790. 'E_FROM_WITHIN',
  791. message:
  792. 'Some logic inside this function\'s implementation encountered an error.\n'+
  793. ' [?] See `.raw` for more details, or visit https://sailsjs.com/support for help.',
  794. }, errCbArg, self._omen);
  795. }//fi
  796. }//fi
  797. // ┬ ┬┌─┐┌─┐┬─┐┬ ┌─┐┌┐┌┌┬┐ ┌─┐┌─┐┌┬┐┌─┐┬─┐┌─┐─┐ ┬┌─┐┌─┐ ┬ ┌─┐┌─┐
  798. // │ │└─┐├┤ ├┬┘│ ├─┤│││ ││ ├─┤├┤ │ ├┤ ├┬┘├┤ ┌┴┬┘├┤ │ │ │ └─┐
  799. // └─┘└─┘└─┘┴└─┴─┘┴ ┴┘└┘─┴┘ ┴ ┴└ ┴ └─┘┴└─└─┘┴ └─└─┘└─┘ ┴─┘└─┘└─┘
  800. // If this Deferred was configured with after-exec lifecycle callbacks from
  801. // userland via .intercept() or .tolerate(), then call those
  802. // lifecycle callbacks now, if appropriate, picking up the potentially-changed
  803. // (even potentially-reconstructed!) error or result.
  804. //
  805. // > Note that this is only relevant if there was an error of some kind.
  806. if (!(self._userlandAfterExecLCs && errCbArg)) {
  807. return proceedToFinalAfterExecLC(errCbArg, resultCbArg, extraCbArgs, self, _cb, handleUncaughtException);
  808. } else {//• </ else there are no userland LCs or we didn't get an error >
  809. // Now before proceeding further, check for a match (if there are any configured).
  810. // Unless we have a match, go ahead and bail.
  811. // > NOTE: We only ever run one of these handlers for any given response!
  812. var matchingUserlandLC;
  813. for (var i = 0; i < self._userlandAfterExecLCs.length; i++) {
  814. var lcDef = self._userlandAfterExecLCs[i];
  815. if (lcDef.rule === undefined) {
  816. matchingUserlandLC = lcDef;
  817. break;
  818. } else if (flaverr.taste(lcDef.rule, errCbArg)) {
  819. matchingUserlandLC = lcDef;
  820. break;
  821. }
  822. }//∞
  823. if (!matchingUserlandLC) {
  824. return proceedToFinalAfterExecLC(errCbArg, resultCbArg, extraCbArgs, self, _cb, handleUncaughtException);
  825. }//• </ if there is no matching userland LC >
  826. (function(proceed){
  827. // Get reasonable default for handler, if no explicit handler function was configured.
  828. if (matchingUserlandLC.handler === undefined && matchingUserlandLC.type === 'tolerate') {
  829. matchingUserlandLC.handler = function(){ return; };//ƒ
  830. }
  831. else if (matchingUserlandLC.handler === undefined && matchingUserlandLC.type === 'intercept') {
  832. matchingUserlandLC.handler = function(err){ return err; };//ƒ
  833. }
  834. // Run userland LC.
  835. self._hasStartedButNotFinishedAfterExecLC = true;
  836. if (!matchingUserlandLC.isThenable) {
  837. var resultFromHandler;
  838. try {
  839. resultFromHandler = matchingUserlandLC.handler(errCbArg);
  840. } catch (err) { return proceed(err); }
  841. return proceed(undefined, resultFromHandler);
  842. } else {
  843. var lcPromise;
  844. try {
  845. lcPromise = matchingUserlandLC.handler(errCbArg);
  846. } catch (err) { return proceed(err); }
  847. lcPromise.then(function(resultFromHandler){
  848. proceed(undefined, resultFromHandler);
  849. }).catch(function(err){
  850. proceed(err);
  851. });//_∏_
  852. }
  853. })(function(err, resultFromHandler){
  854. // Clear spinlock.
  855. self._hasStartedButNotFinishedAfterExecLC = false;
  856. if (err) {
  857. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  858. // FUTURE: (Maybe) Specifically for `.tolerate()`, allow throwing special exit signals
  859. // from within the handler.
  860. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  861. // If this is an .intercept() handler, then it's possible the handler threw on purpose,
  862. // perhaps because it was attempting to send a special signal to its caller (e.g. the
  863. // implementation of an action/helper/etc) where it presumably has a special meaning.
  864. // So in this case, we customize the error message to reflect that possibility and to
  865. // suggest an appropriate resolution.
  866. if (matchingUserlandLC.type === 'intercept') {
  867. return proceedToFinalAfterExecLC(flaverr({
  868. name:
  869. 'UsageError',
  870. message:
  871. 'Caught unexpected error in `.intercept()` handler, which should not throw:\n'+
  872. flaverr.parseOrBuildError(err).message+'\n'+
  873. 'If this was intentional, i.e. to communicate a signal to the caller, then\n'+
  874. 'please just return the new or modified error you would like to use instead.\n'+
  875. 'The value returned to `.intercept()` will be used as the new Error.\n'+
  876. ' [?] See https://sailsjs.com/support for help.',
  877. raw:
  878. err
  879. }, self._omen), resultCbArg, extraCbArgs, self, _cb, handleUncaughtException);
  880. }
  881. else {
  882. // Otherwise, we'll allow this error through unscathed and treat it just like
  883. // any unexpected error that might have been thrown by the implementation.
  884. return proceedToFinalAfterExecLC(err, resultCbArg, extraCbArgs, self, _cb, handleUncaughtException);
  885. }
  886. }//•
  887. // Now swallow or swap out the error, if instructed to do so.
  888. // Swallow:
  889. // > i.e. if a matching `.tolerate()` was encountered, then consider
  890. // > this successful no matter what, and use the value returned by the
  891. // > LC as the new result.
  892. if (matchingUserlandLC.type === 'tolerate') {
  893. errCbArg = undefined;
  894. resultCbArg = resultFromHandler;
  895. }
  896. // Swap:
  897. //
  898. // > i.e. if a matching `.intercept()` was encountered, then consider
  899. // > whatever the intercept handler returned to be our new Error.
  900. else if (matchingUserlandLC.type === 'intercept') {
  901. // If the handler returned `undefined`, then fail with an error.
  902. // (this shouldn't happen, an indicates invalid usage)
  903. if (resultFromHandler === undefined) {
  904. return proceedToFinalAfterExecLC(flaverr({
  905. name:
  906. 'UsageError',
  907. message:
  908. '`.intercept()` handler returned `undefined`, but this should never happen.\n'+
  909. 'Regardless, here is a summary of the original underlying error:\n'+
  910. flaverr.parseOrBuildError(errCbArg).message+'\n'+
  911. ' [?] See https://sailsjs.com/support for help.',
  912. raw:
  913. errCbArg
  914. }, self._omen), resultCbArg, extraCbArgs, self, _cb, handleUncaughtException);
  915. }//•
  916. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  917. // Normally, these errors must ALWAYS be Error instances already.
  918. // But for this special case, where the original Error value
  919. // is being overridden through the use of `.intercept()`, we'd
  920. // LIKE to make a special exception to the rule (no pun intended.)
  921. //
  922. // There's only one problem: Because of bluebird's "maybeWrapAsError"
  923. // implementation, we can't send certain non-Errors through to it
  924. // (specifically primitives) because they get autowrapped.
  925. //
  926. // > Here's the relevant bit of code:
  927. // > https://github.com/petkaantonov/bluebird/blob/e8d8525a0517280d11d6c77ae6b61df86419232b/src/promisify.js#L182-L184
  928. //
  929. // Again, most of the time, this would be fine. But while bluebird's
  930. // looking out for us here is admirable, there are some situations.
  931. // where this is not welcome -- such as when trying to throw a string.
  932. //
  933. // > Why throw a string?
  934. // > This is useful for throwing special signals-- e.g. from the inside
  935. // > of an actions2 action or a helper in Sails, a machine's fn in a
  936. // > machinepack, or from a commandline script.
  937. //
  938. // So anyway, to work around this, we have to come up with a consistent
  939. // way of wrapping up non-Errors to look like Errors. That's what we
  940. // do next.
  941. //
  942. // ** Note that we also do this in a couple of other places in parley. **
  943. // ** (look for `flaverr.parseOrBuildError()` calls) **
  944. //
  945. // > (If ever we find ourselves wanting to revert this approach, the old
  946. // > code that used to check for non-Errors was removed in parley@376208fd1c0ab70e7a6b9c4ecfa563ec0d77a3a8.
  947. // > But... as mentioned above-- there are some good reasons to keep things
  948. // > the new way that they are now.)
  949. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  950. var interceptError = flaverr.parseOrBuildError(resultFromHandler, self._omen);
  951. errCbArg = interceptError;
  952. }//fi </ if this is an .intercept() >
  953. return proceedToFinalAfterExecLC(errCbArg, resultCbArg, extraCbArgs, self, _cb, handleUncaughtException);
  954. });//_∏_ (†)
  955. }//fi
  956. }//ƒ </ definition of `proceedToInterceptsAndChecks` >
  957. /**
  958. * Used exclusively by `proceedToInterceptsAndChecks()`, this function is an optimization.
  959. * It would be much better to use an IIFE instead of defining this function, but we're
  960. * dealing with a very hot code path, so the performance gain is worth it.
  961. * That said, this optimization should never be applied in normal userland code!
  962. */
  963. function proceedToFinalAfterExecLC(errCbArg, resultCbArg, extraCbArgs, self, _cb, handleUncaughtException) {
  964. // ╔═╗╦╔╗╔╔═╗╦ ┌─┐┌─┐┌┬┐┌─┐┬─┐┌─┐─┐ ┬┌─┐┌─┐ ┬ ┌─┐
  965. // ╠╣ ║║║║╠═╣║ ├─┤├┤ │ ├┤ ├┬┘├┤ ┌┴┬┘├┤ │ │ │
  966. // ╚ ╩╝╚╝╩ ╩╩═╝ ┴ ┴└ ┴ └─┘┴└─└─┘┴ └─└─┘└─┘ ┴─┘└─┘
  967. // ┌─┐┬─┐┌─┐┌┬┐ ┬┌┬┐┌─┐┬ ┌─┐┌┬┐┌─┐┌┐┌┌┬┐┌─┐┬─┐┬ ┌─┐┌┐┌┌┬┐
  968. // ├┤ ├┬┘│ ││││ ││││├─┘│ ├┤ │││├┤ │││ │ │ │├┬┘│ ├─┤│││ ││
  969. // └ ┴└─└─┘┴ ┴ ┴┴ ┴┴ ┴─┘└─┘┴ ┴└─┘┘└┘ ┴ └─┘┴└─┴─┘┴ ┴┘└┘─┴┘
  970. // If this Deferred was built with an `finalAfterExecLC` lifecycle callback,
  971. // then intercept our normal flow to call that lifecycle callback, picking up
  972. // the potentially-changed (even potentially-reconstructed!) error or result.
  973. if (self._finalAfterExecLC) {
  974. if (errCbArg) {
  975. errCbArg = self._finalAfterExecLC(errCbArg);
  976. }
  977. else {
  978. resultCbArg = self._finalAfterExecLC(undefined, resultCbArg);
  979. }
  980. }//fi
  981. // ┌┐┌┌─┐┬ ┬ ┌─┐┌─┐┌┬┐┬ ┬┌─┐┬ ┬ ┬ ┬ ┌┬┐┬─┐┬┌─┐┌─┐┌─┐┬─┐ ┌─┐┌┐ ┌─┐┌┐┌┌─┐┬ ┬┬─┐┬┌┐┌┌─┐
  982. // ││││ ││││ ├─┤│ │ │ │├─┤│ │ └┬┘ │ ├┬┘││ ┬│ ┬├┤ ├┬┘ │ ├┴┐ ├┤ │││└─┐│ │├┬┘│││││ ┬
  983. // ┘└┘└─┘└┴┘ ┴ ┴└─┘ ┴ └─┘┴ ┴┴─┘┴─┘┴ ┴ ┴└─┴└─┘└─┘└─┘┴└─ └─┘└─┘┘ └─┘┘└┘└─┘└─┘┴└─┴┘└┘└─┘
  984. // ┌┐┌┌─┐ ┌─┐┌─┐┌─┐┬┌┬┐┌─┐┌┐┌┌┬┐┌─┐┬ ┌─┐┬ ┬┌─┐┬ ┬ ┌─┐┬ ┬┬┌┐┌┌─┐ ┌─┐┌─┐ ┌─┐┬─┐┬─┐┌─┐┬─┐┌─┐
  985. // ││││ │ ├─┤│ │ │ ││├┤ │││ │ ├─┤│ └─┐│││├─┤│ │ │ │││││││││ ┬ │ │├┤ ├┤ ├┬┘├┬┘│ │├┬┘└─┐
  986. // ┘└┘└─┘ ┴ ┴└─┘└─┘┴─┴┘└─┘┘└┘ ┴ ┴ ┴┴─┘ └─┘└┴┘┴ ┴┴─┘┴─┘└─┘└┴┘┴┘└┘└─┘ └─┘└ └─┘┴└─┴└─└─┘┴└─└─┘
  987. // ┌─┐┬─┐┌─┐┌┬┐ ┌─┐┌─┐┬ ┬ ┌┐ ┌─┐┌─┐┬┌─┌─┐
  988. // ├┤ ├┬┘│ ││││ │ ├─┤│ │ ├┴┐├─┤│ ├┴┐└─┐
  989. // └ ┴└─└─┘┴ ┴ └─┘┴ ┴┴─┘┴─┘└─┘┴ ┴└─┘┴ ┴└─┘
  990. // If there are any extra arguments, send them back too.
  991. // (This is unconventional, but permitted to allow for extra metadata,
  992. // which is sometimes handy when you want to expose advanced usage.)
  993. // Otherwise, if there's no result, just call the callback w/ no args.
  994. // (This just makes for better log output, etc.)
  995. // More on that below!
  996. // If this callback is being called after at least one tick has elapsed...
  997. if (self._hasAlreadyWaitedAtLeastOneTick) {
  998. // If 2nd argument (handleUncaughtException) was provided to .exec(), then run that
  999. // instead of throwing. This protects against unexpected, uncaught exceptions in
  1000. // asynchronous callbacks.
  1001. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  1002. // FUTURE: Probably deprecate this, then remove support (see above).
  1003. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  1004. if (handleUncaughtException) {
  1005. try {
  1006. if (extraCbArgs) {
  1007. return _cb.apply(undefined, [errCbArg, resultCbArg].concat(extraCbArgs));
  1008. } else if (errCbArg !== undefined) {
  1009. return _cb(errCbArg);
  1010. } else if (resultCbArg !== undefined) {
  1011. return _cb(undefined, resultCbArg);
  1012. } else {
  1013. return _cb();
  1014. }
  1015. } catch (unexpectedErrorFromCallback) {
  1016. return handleUncaughtException(unexpectedErrorFromCallback);
  1017. }
  1018. }//•
  1019. // Otherwise, just trigger the callback as-is.
  1020. // (If it throws, it will crash the process!)
  1021. if (extraCbArgs) {
  1022. return _cb.apply(undefined, [errCbArg, resultCbArg].concat(extraCbArgs));
  1023. } else if (errCbArg !== undefined) {
  1024. return _cb(errCbArg);
  1025. } else if (resultCbArg !== undefined) {
  1026. return _cb(undefined, resultCbArg);
  1027. } else {
  1028. return _cb();
  1029. }
  1030. }//•
  1031. //‡
  1032. // Otherwise, our logic is synchronous (i.e. <1 async tick has elapsed at the time it's being
  1033. // called). So wrap the `_cb` from userland in a try/catch. If an unhandled error of any kind
  1034. // is thrown from the userland cb, our wrapper uses a special Envelope to bust out of the `try`
  1035. // block, ensuring that the unhandled exception is thrown up to userland.
  1036. //
  1037. // > NOTE:
  1038. // > Without this extra code here, we'd end up with the old behavior: outputting a puzzling error
  1039. // > message -- e.g. about something unexpected things happening in the Deferred, or a warning
  1040. // > about triggering the callback twice (when actually, the issue is that something went wrong
  1041. // > in the callback-- and that the Deferred logic happened to be synchronous, so it wasn't able
  1042. // > to escape parley's internal `try` block.)
  1043. // >
  1044. // > Some relevant links for reference:
  1045. // > • https://github.com/node-machine/machine/blob/7fdcf8a869605d0951909725061379cd27bd7f0d/lib/private/intercept-exit-callbacks.js#L186-L238
  1046. // > • https://github.com/node-machine/machine/search?utf8=%E2%9C%93&q=_hasFnYieldedYet&type=
  1047. // > • https://github.com/node-machine/machine/search?utf8=%E2%9C%93&q=_runningSynchronously&type=
  1048. else {
  1049. try {
  1050. if (extraCbArgs) {
  1051. return _cb.apply(undefined, [errCbArg, resultCbArg].concat(extraCbArgs));
  1052. } else if (errCbArg !== undefined) {
  1053. return _cb(errCbArg);
  1054. } else if (resultCbArg !== undefined) {
  1055. return _cb(undefined, resultCbArg);
  1056. } else {
  1057. return _cb();
  1058. }
  1059. } catch (unexpectedErrorFromCallback) {
  1060. throw flaverr.wrap({
  1061. code: 'E_ESCAPE_HATCH',
  1062. traceRef: self
  1063. }, unexpectedErrorFromCallback, self._omen);
  1064. }
  1065. }//•
  1066. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  1067. // FUTURE: Additionally, this additional layer of wrapping could take care of improving
  1068. // stack traces, even in the case where an Error comes up from inside the implementation.
  1069. // If done carefully, this can be done in a way that protects characteristics of the
  1070. // internal Error (e.g. its "code", etc.), while also providing a better stack trace.
  1071. //
  1072. // For example, something like this:
  1073. // ```
  1074. // var relevantPropNames = _.difference(
  1075. // _.union(
  1076. // ['name', 'message'],
  1077. // Object.getOwnPropertyNames(underlyingError)
  1078. // ),
  1079. // ['stack']
  1080. // );
  1081. // var errTemplate = _.pick(underlyingError, relevantPropNames);
  1082. // errTemplate.raw = underlyingError;//<< could override stuff-- that's ok (see below).
  1083. // var newError = flaverr(errTemplate, omen);
  1084. // ```
  1085. // > Note that, above, we also kept the original error (and thus _its_ trace) and
  1086. // > attached that as a separate property. If the original error already has "raw",
  1087. // > that's ok. This is one thing that it makes sense for us to mutate-- and any
  1088. // > attempt to do otherwise would probably be more confusing (you can imagine a while
  1089. // > loop where we add underscores in front of the string "raw", and use that as a keyname.
  1090. // > But again, that ends up being more confusing from a userland perspective.)
  1091. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  1092. }//ƒ
  1093. /**
  1094. * bindUserlandAfterExecLC()
  1095. *
  1096. * Used exclusively by `Deferred.prototype.intercept()` and `Deferred.prototype.tolerate()`,
  1097. * this function is an optimization.
  1098. * It would be much better to use an IIFE instead of defining this function, but we're
  1099. * dealing with a very hot code path, so the performance gain is worth it.
  1100. * That said, this optimization should never be applied in normal userland code!
  1101. *
  1102. * @param {String} lcType
  1103. * @param {String|Dictionary|Function} negotiationRuleOrWildcardHandler
  1104. * @param {Function?} specificHandler
  1105. * @param {Deferred} deferred
  1106. * @param {Dictionary?} _lcOpts
  1107. *
  1108. *
  1109. * > The lifecycle callback attached here will run *before* this Deferred's
  1110. * > `_finalAfterExecLC` function (if it has one configured from implementorland.)
  1111. * >
  1112. * > Historical notes:
  1113. * > https://gist.github.com/mikermcneil/c1bc2d57f5bedae810295e5ed8c5f935
  1114. */
  1115. function bindUserlandAfterExecLC(lcType, negotiationRuleOrWildcardHandler, specificHandler, deferred, _lcOpts){
  1116. // Handle variadic usage.
  1117. var handler;
  1118. var negotiationRule;
  1119. if (_.isFunction(negotiationRuleOrWildcardHandler) && specificHandler === undefined) {
  1120. handler = negotiationRuleOrWildcardHandler;
  1121. }
  1122. else {
  1123. negotiationRule = negotiationRuleOrWildcardHandler;
  1124. handler = specificHandler;
  1125. }
  1126. // Validate arguments.
  1127. if (handler !== undefined && !_.isFunction(handler)) {
  1128. throw flaverr({
  1129. name:
  1130. 'UsageError',
  1131. message:
  1132. 'Invalid usage of `.'+lcType+'()`. Provided handler function is invalid.\n'+
  1133. ' [?] For advice or assistance, come visit https://sailsjs.com/support'
  1134. }, deferred._omen);
  1135. }//•
  1136. if (handler === undefined && lcType === 'intercept') {
  1137. throw flaverr({
  1138. name:
  1139. 'UsageError',
  1140. message:
  1141. 'Invalid usage of `.intercept()`. No handler function provided.\n'+
  1142. ' [?] See https://sailsjs.com/support for help.'
  1143. }, deferred._omen);
  1144. }//•
  1145. if (handler === undefined && negotiationRule === undefined && lcType === 'tolerate') {
  1146. throw flaverr({
  1147. name:
  1148. 'UsageError',
  1149. message:
  1150. 'Invalid usage of `.tolerate()`. No handler function was provided, and no\n'+
  1151. 'negotiation rule was provided either. It would be unsafe to continue.\n'+
  1152. 'It is never a good idea to tolerate *ALL* errors a function might\n'+
  1153. 'encounter, because doing so would make it easy to accidentally swallow\n'+
  1154. 'real problems or bugs. So instead, please provide some way of narrowing\n'+
  1155. 'down the errors which you\'d like to tolerate, like `.tolerate(\'E_FOOBAR\')`.\n'+
  1156. ' [?] See https://sailsjs.com/support for help.'
  1157. }, deferred._omen);
  1158. }//•
  1159. if (negotiationRule !== undefined) {
  1160. if (_.isString(negotiationRule) && negotiationRule) {
  1161. // Ok, we'll assume it's fine.
  1162. }
  1163. else if (_.isArray(negotiationRule)) {
  1164. // you can bind multiple LCs at the same time
  1165. // (array rules are automatically split into sub-rules)
  1166. }
  1167. else if (_.isObject(negotiationRule) && !_.isArray(negotiationRule) && !_.isFunction(negotiationRule)) {
  1168. // flaverr/bluebird/lodash-style dictionary negotiation rules are now supported.
  1169. }
  1170. else {
  1171. throw flaverr({
  1172. name:
  1173. 'UsageError',
  1174. message:
  1175. 'Invalid usage of `.'+lcType+'()`. Invalid error negotiation rule: `'+util.inspect(negotiationRule,{depth:null})+'`.\n'+
  1176. ' [?] For advice or assistance, come visit https://sailsjs.com/support'
  1177. }, deferred._omen);
  1178. }
  1179. }//fi
  1180. // Determine whether the handler function is a "thenable".
  1181. // (The `_lcOpts` approach allows backwards compat. for older versions of Node.)
  1182. var isThenable = (
  1183. (handler && handler.constructor.name === 'AsyncFunction') ||
  1184. (_.isObject(_lcOpts) && _lcOpts.thenable)
  1185. );
  1186. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  1187. // FUTURE: MAYBE add a best-effort check to make sure there is no pre-existing
  1188. // after exec LC rule that matches this one (i.e. already previously registered
  1189. // using .tolerate() or .intercept())
  1190. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  1191. if (!deferred._userlandAfterExecLCs) {
  1192. deferred._userlandAfterExecLCs = [];
  1193. }//fi
  1194. if (_.isArray(negotiationRule)) {
  1195. for (var i=0; i<negotiationRule.length; i++) {
  1196. deferred._userlandAfterExecLCs.push({
  1197. type: lcType,
  1198. rule: negotiationRule[i],
  1199. handler: handler,
  1200. isThenable: isThenable
  1201. });
  1202. }//∞
  1203. }
  1204. else {
  1205. deferred._userlandAfterExecLCs.push({
  1206. type: lcType,
  1207. rule: negotiationRule,
  1208. handler: handler,
  1209. isThenable: isThenable
  1210. });
  1211. }
  1212. return deferred;
  1213. }//ƒ