123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350 |
- /**
- * Module dependencies
- */
- var util = require('util');
- var _ = require('@sailshq/lodash');
- var flaverr = require('flaverr');
- var Deferred = require('./private/Deferred');
- // Optimization: Pull process env check up here.
- var IS_DEBUG_OR_NON_PRODUCTION_ENV = (
- process.env.NODE_ENV !== 'production' ||
- process.env.DEBUG
- );
- /**
- * parley()
- *
- * Build a deferred object that supports Node-style callbacks and promises.
- * > See README.md for more details.
- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- * @param {Function} handleExec
- * The `handleExec` function to call (either immediately or when the Deferred
- * is executed, depending on whether an explicit cb was provided)
- *
- * @param {Function?} explicitCbMaybe
- * An optional parameter that, if specified, is passed directly as the incoming
- * `done` argument to your "handleExec" handler function (i.e. _its_ callback).
- * Otherwise, if it is omitted, then handleExec receives an internally-generated
- * callback (from parley) as its `done` argument. When called, this implicit `done`
- * will appropriately dispatch with the deferred object. Finally, note that if an
- * explicit callback is provided, parley will return undefined instead of returning
- * a Deferred.
- * > The nice thing about this is that it allows implementor code that provides this
- * > feature to avoid manually duplicating the branching logic (i.e. the code that
- * > checks to see if an explicit cb was provided, and if not, returns a new Deferred)
- *
- * @param {Dictionary?} customMethods
- * An optional dictionary of custom functions that, if specified, will be used to extend
- * the Deferred object. It omitted, then only the default methods like `.exec()` will
- * exist.
- * > e.g.
- * > ```
- * > {
- * > where: function (whereClause) {
- * > this._criteria = this._criteria || {};
- * > this._criteria.where = whereClause;
- * > return this;
- * > },
- * > foo: function(){...},
- * > bar: function(){...},
- * > ...
- * > }
- *
- * @param {Number?} timeout
- * Optional. If specified, timeouts will be enabled, and this number will indicate
- * the max # of milliseconds to let the `handleExec` logic run before giving up and
- * failing with a TimeoutError. (To disable timeouts, leave this undefined.)
- *
- * @param {Error?} omen
- * An optional omen to use for improving the stack trace, in the event of an error.
- *
- * @param {Function?} finalAfterExecLC
- * An optional, synchronous handler function for intercepting the arguments to .exec()'s
- * callback. Only applicable when using the Deferred-style usage-- including when using
- * promises via ES8's async/await or .then()/.catch(). Receives the `(err, result)`
- * function signature, where either `err` is an Error instance and `result` is undefined,
- * or `err` is undefined and `result` may or may not exist. In any case, if specified,
- * this handler function MUST not throw, and it MUST respect standard node-style callback
- * conventions insofar as how it decides a return value. For example, if it receives `err`,
- * it must return an Error instance. (ENFORCING THIS IS UP TO YOUR CODE!)
- * > NOTE: The purpose of this handler is to allow for changing the behavior of .exec()
- * > without necessarily calling it, or reinventing the wheel and creating our own version.
- * > For example, we might want to include nicer, more customized error messages. Or we
- * > might want to intercept built-in error scenarios that would be otherwise difficult to
- * > capture-- things like timeouts, double-invocation, and unhandled throwing.
- *
- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- * @returns {Deferred}
- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- * @throws {Error} If there are unexpected usage problems with how parley() itself is called
- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- */
- module.exports = function parley(handleExec, explicitCbMaybe, customMethods, timeout, omen, finalAfterExecLC){
- // A few (very carefully picked) sanity checks for implementors.
- //
- // > Note that we deliberately use `typeof` instead of _.isFunction() for performance.
- if (!handleExec) {
- throw new Error('Consistency violation: Must specify a first argument when calling parley() -- please provide a `handleExec` function or a dictionary of options');
- }
- if (typeof handleExec !== 'function') {
- throw new Error('Consistency violation: First argument to parley() should be a function. But instead, got: '+util.inspect(handleExec, {depth:2})+'');
- }
- //==========================================================================================
- // ALL OTHER **IMPLEMENTOR** USAGE CHECKS WERE REMOVED FOR PERFORMANCE REASONS.
- //
- // > Check out this commit for more of the original code:
- // > https://github.com/mikermcneil/parley/commit/e7ec7e445e2a502b9fcb57bc746c7b9714d3cf16
- // >
- // > Or for another example of a simple check that would pack a 24% performance hit
- // > for building Deferreds, see:
- // > https://github.com/mikermcneil/parley/commit/7d475d0c2165b683d8d5af98a4d073875f14cbd3
- // >
- // > Also note we still do a few (very carefully picked) validations for things that could
- // > affect end users of parley-implementing functions -- i.e. code that calls .exec() twice,
- // > etc. That's all handled elsewhere (where the exec() method is defined.)
- //==========================================================================================
- // ██╗ ██╗ █████╗ ███╗ ██╗██████╗ ██╗ ███████╗
- // ██║ ██║██╔══██╗████╗ ██║██╔══██╗██║ ██╔════╝
- // ███████║███████║██╔██╗ ██║██║ ██║██║ █████╗
- // ██╔══██║██╔══██║██║╚██╗██║██║ ██║██║ ██╔══╝
- // ██║ ██║██║ ██║██║ ╚████║██████╔╝███████╗███████╗
- // ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═════╝ ╚══════╝╚══════╝
- //
- // ███████╗██╗ ██╗██████╗ ██╗ ██╗ ██████╗██╗████████╗ ██████╗██████╗
- // ██╔════╝╚██╗██╔╝██╔══██╗██║ ██║██╔════╝██║╚══██╔══╝ ██╔════╝██╔══██╗
- // █████╗ ╚███╔╝ ██████╔╝██║ ██║██║ ██║ ██║ ██║ ██████╔╝
- // ██╔══╝ ██╔██╗ ██╔═══╝ ██║ ██║██║ ██║ ██║ ██║ ██╔══██╗
- // ███████╗██╔╝ ██╗██║ ███████╗██║╚██████╗██║ ██║ ╚██████╗██████╔╝
- // ╚══════╝╚═╝ ╚═╝╚═╝ ╚══════╝╚═╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝╚═════╝
- //
- // ╦╔═╗ ┌─┐┬─┐┌─┐┬ ┬┬┌┬┐┌─┐┌┬┐
- // ║╠╣ ├─┘├┬┘│ │└┐┌┘│ ││├┤ ││
- // ╩╚ ┴ ┴└─└─┘ └┘ ┴─┴┘└─┘─┴┘
- // If explicitCb provided, run the handleExec logic, then call the explicit callback.
- //
- // > All of the additional checks from below (e.g. try/catch) are NOT performed
- // > in the situation where an explicit callback was provided. This is to allow
- // > for userland code to squeeze better performance out of particular method calls
- // > by simply passing through the callback directly.
- // > (As a bonus, it also avoids duplicating the code below in this file.)
- if (explicitCbMaybe) {
- handleExec(explicitCbMaybe);
- // ██████╗ ███████╗████████╗██╗ ██╗██████╗ ███╗ ██╗
- // ██╔══██╗██╔════╝╚══██╔══╝██║ ██║██╔══██╗████╗ ██║
- // ██████╔╝█████╗ ██║ ██║ ██║██████╔╝██╔██╗ ██║
- // ██╔══██╗██╔══╝ ██║ ██║ ██║██╔══██╗██║╚██╗██║
- // ██║ ██║███████╗ ██║ ╚██████╔╝██║ ██║██║ ╚████║
- // ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝
- //
- // ██╗ ██╗███╗ ██╗██████╗ ███████╗███████╗██╗███╗ ██╗███████╗██████╗
- // ██║ ██║████╗ ██║██╔══██╗██╔════╝██╔════╝██║████╗ ██║██╔════╝██╔══██╗
- // ██║ ██║██╔██╗ ██║██║ ██║█████╗ █████╗ ██║██╔██╗ ██║█████╗ ██║ ██║
- // ██║ ██║██║╚██╗██║██║ ██║██╔══╝ ██╔══╝ ██║██║╚██╗██║██╔══╝ ██║ ██║
- // ╚██████╔╝██║ ╚████║██████╔╝███████╗██║ ██║██║ ╚████║███████╗██████╔╝
- // ╚═════╝ ╚═╝ ╚═══╝╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚═════╝
- //
- return;
- }//-•
- // Otherwise, no explicit callback was provided- so we'll build & return a Deferred...
- // ██████╗ ████████╗██╗ ██╗███████╗██████╗ ██╗ ██╗██╗███████╗███████╗
- // ██╔═══██╗╚══██╔══╝██║ ██║██╔════╝██╔══██╗██║ ██║██║██╔════╝██╔════╝██╗
- // ██║ ██║ ██║ ███████║█████╗ ██████╔╝██║ █╗ ██║██║███████╗█████╗ ╚═╝
- // ██║ ██║ ██║ ██╔══██║██╔══╝ ██╔══██╗██║███╗██║██║╚════██║██╔══╝ ██╗
- // ╚██████╔╝ ██║ ██║ ██║███████╗██║ ██║╚███╔███╔╝██║███████║███████╗╚═╝
- // ╚═════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝ ╚═╝╚══════╝╚══════╝
- //
- // ██████╗ ██╗ ██╗██╗██╗ ██████╗
- // ██╔══██╗██║ ██║██║██║ ██╔══██╗
- // ██████╔╝██║ ██║██║██║ ██║ ██║
- // ██╔══██╗██║ ██║██║██║ ██║ ██║
- // ██████╔╝╚██████╔╝██║███████╗██████╔╝
- // ╚═════╝ ╚═════╝ ╚═╝╚══════╝╚═════╝
- //
- // ██████╗ ███████╗███████╗███████╗██████╗ ██████╗ ███████╗██████╗
- // ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗██╔══██╗██╔════╝██╔══██╗
- // ██║ ██║█████╗ █████╗ █████╗ ██████╔╝██████╔╝█████╗ ██║ ██║
- // ██║ ██║██╔══╝ ██╔══╝ ██╔══╝ ██╔══██╗██╔══██╗██╔══╝ ██║ ██║
- // ██████╔╝███████╗██║ ███████╗██║ ██║██║ ██║███████╗██████╔╝
- // ╚═════╝ ╚══════╝╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═════╝
- //
- // Build deferred object.
- //
- // > For more info & benchmarks, see:
- // > https://github.com/mikermcneil/parley/commit/5996651c4b15c7850b5eb2e4dc038e8202414553#commitcomment-20256030
- // >
- // > And also `baseline.benchmark.js` in this repo.
- // >
- // > But then also see:
- // > https://github.com/mikermcneil/parley/commit/023dc9396bdfcd02290624ca23cb2d005037f398
- // >
- // > (Basically, it keeps going back and forth between this and closures, but after a lot
- // > of experimentation, the prototypal approach seems better for overall performance.)
- var π = new Deferred(handleExec);
- // If appropriate, start the 15 second .exec() countdown.
- var EXEC_COUNTDOWN_IN_SECONDS = 15;
- if (IS_DEBUG_OR_NON_PRODUCTION_ENV) {
- π._execCountdown = setTimeout(function(){
- // IWMIH, it means that this Deferred hasn't begun executing,
- // even after 15 seconds. This deserves a warning log.
- // > See https://trello.com/c/7QnQZ6aC for more background.
- console.warn(
- '- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n'+
- 'WARNING: A function that was initially called over '+EXEC_COUNTDOWN_IN_SECONDS+' seconds\n'+
- 'ago has still not actually been executed. Any chance the\n'+
- 'source code is missing an "await"?\n'+
- '\n'+
- (π._omen?(
- 'To assist you in hunting this down, here is a stack trace:\n'+
- '```\n'+
- flaverr.getBareTrace(π._omen)+'\n'+
- '```\n'+
- '\n'
- ):'')+
- // 'Please double-check this code is not missing an "await".\n'+
- // '\n'+
- ' [?] For more help, visit https://sailsjs.com/support\n'+
- '- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -'
- );
- }, EXEC_COUNTDOWN_IN_SECONDS*1000);
- }//fi
- // ┌─┐┌┬┐┌┬┐┌─┐┌─┐┬ ┬ ╔═╗╦ ╦╔═╗╔╦╗╔═╗╔╦╗ ╔╦╗╔═╗╔╦╗╦ ╦╔═╗╔╦╗╔═╗
- // ├─┤ │ │ ├─┤│ ├─┤ ║ ║ ║╚═╗ ║ ║ ║║║║ ║║║║╣ ║ ╠═╣║ ║ ║║╚═╗
- // ┴ ┴ ┴ ┴ ┴ ┴└─┘┴ ┴ ╚═╝╚═╝╚═╝ ╩ ╚═╝╩ ╩ ╩ ╩╚═╝ ╩ ╩ ╩╚═╝═╩╝╚═╝
- // ┌─ ┬┌─┐ ┬─┐┌─┐┬ ┌─┐┬ ┬┌─┐┌┐┌┌┬┐ ─┐
- // │─── │├┤ ├┬┘├┤ │ ├┤ └┐┌┘├─┤│││ │ ───│
- // └─ ┴└ ┴└─└─┘┴─┘└─┘ └┘ ┴ ┴┘└┘ ┴ ─┘
- // If a dictionary of `customMethods` were provided, attach them dynamically.
- if (customMethods) {
- // Even with no contents, using an _.each() loop here would actually hurt the performance
- // of the "just_build" benchmark by 93% (~13x as slow). Granted, it was very fast to
- // begin with... but compare with this `for` loop which, with no contents, only hurts
- // the same benchmark's performance by 25% ~(1.3x as slow).
- var methodFn;
- for (var methodName in customMethods) {
- // We explicitly prevent overriding:
- if (
- // • built-in methods:
- methodName === 'exec' ||
- methodName === 'then' ||
- methodName === 'catch' ||
- methodName === 'toPromise' ||
- methodName === 'intercept' ||
- methodName === 'tolerate' ||
- // (Note that we explicitly omit `.log()`, `.now()`, and `.timeout()`
- // so that they may be potentially overridden.)
- // • other special, private properties:
- methodName === '_execCountdown' ||
- methodName === '_hasBegunExecuting' ||
- methodName === '_hasFinishedExecuting' ||
- methodName === '_hasStartedButNotFinishedAfterExecLC' ||
- methodName === '_hasAlreadyWaitedAtLeastOneTick' ||
- methodName === '_skipImplSpinlockWarning' ||
- methodName === '_hasTimedOut' ||
- methodName === '_handleExec' ||
- methodName === '_promise' ||
- methodName === '_timeout' ||
- methodName === '_omen' ||
- methodName === '_userlandAfterExecLCs' ||
- methodName === '_finalAfterExecLC' ||
- // • the standard JavaScript object flora:
- methodName === '__defineGetter__' ||
- methodName === '__defineSetter__' ||
- methodName === '__lookupGetter__' ||
- methodName === '__lookupSetter__' ||
- methodName === '__proto__' ||
- methodName === 'constructor' ||
- methodName === 'hasOwnProperty' ||
- methodName === 'isPrototypeOf' ||
- methodName === 'propertyIsEnumerable' ||
- methodName === 'toLocaleString' ||
- methodName === 'toString' ||
- methodName === 'valueOf' ||
- // • and things that are just a really bad idea:
- // (or at the very least, which shouldn't be defined this way)
- methodName === 'prototype' ||
- methodName === 'toJSON' ||
- methodName === 'inspect'
- ) {
- throw new Error('Cannot define custom method (`.'+methodName+'()`) because `'+methodName+'` is a reserved/built-in property.');
- }
- methodFn = customMethods[methodName];
- π[methodName] = methodFn;
- }//</for>
- }//>-
- // ┌─┐┌┬┐┌┬┐┌─┐┌─┐┬ ┬ ╔╦╗╦╔╦╗╔═╗╔═╗╦ ╦╔╦╗
- // ├─┤ │ │ ├─┤│ ├─┤ ║ ║║║║║╣ ║ ║║ ║ ║
- // ┴ ┴ ┴ ┴ ┴ ┴└─┘┴ ┴ ╩ ╩╩ ╩╚═╝╚═╝╚═╝ ╩
- // ┌─ ┬┌─┐ ┬─┐┌─┐┬ ┌─┐┬ ┬┌─┐┌┐┌┌┬┐ ─┐
- // │─── │├┤ ├┬┘├┤ │ ├┤ └┐┌┘├─┤│││ │ ───│
- // └─ ┴└ ┴└─└─┘┴─┘└─┘ └┘ ┴ ┴┘└┘ ┴ ─┘
- if (timeout) {
- 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})+''); }
- π._timeout = timeout;
- }
- // ┌─┐┌┬┐┌┬┐┌─┐┌─┐┬ ┬ ╔═╗╔╦╗╔═╗╔╗╔
- // ├─┤ │ │ ├─┤│ ├─┤ ║ ║║║║║╣ ║║║
- // ┴ ┴ ┴ ┴ ┴ ┴└─┘┴ ┴ ╚═╝╩ ╩╚═╝╝╚╝
- // ┌─ ┬┌─┐ ┬─┐┌─┐┬ ┌─┐┬ ┬┌─┐┌┐┌┌┬┐ ─┐
- // │─── │├┤ ├┬┘├┤ │ ├┤ └┐┌┘├─┤│││ │ ───│
- // └─ ┴└ ┴└─└─┘┴─┘└─┘ └┘ ┴ ┴┘└┘ ┴ ─┘
- if (omen) {
- 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})+''); }
- π._omen = omen;
- }
- // ┌─┐┌┬┐┌┬┐┌─┐┌─┐┬ ┬ ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔═╗╔╦╗╔═╗╔═╗╔╦╗╔═╗╦═╗╔═╗═╗ ╦╔═╗╔═╗
- // ├─┤ │ │ ├─┤│ ├─┤ ║║║║ ║ ║╣ ╠╦╝║ ║╣ ╠═╝ ║ ╠═╣╠╣ ║ ║╣ ╠╦╝║╣ ╔╩╦╝║╣ ║
- // ┴ ┴ ┴ ┴ ┴ ┴└─┘┴ ┴ ╩╝╚╝ ╩ ╚═╝╩╚═╚═╝╚═╝╩ ╩ ╩ ╩╚ ╩ ╚═╝╩╚═╚═╝╩ ╚═╚═╝╚═╝
- // ┌─ ┬┌─┐ ┬─┐┌─┐┬ ┌─┐┬ ┬┌─┐┌┐┌┌┬┐ ─┐
- // │─── │├┤ ├┬┘├┤ │ ├┤ └┐┌┘├─┤│││ │ ───│
- // └─ ┴└ ┴└─└─┘┴─┘└─┘ └┘ ┴ ┴┘└┘ ┴ ─┘
- if (finalAfterExecLC) {
- 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})+''); }
- π._finalAfterExecLC = finalAfterExecLC;
- }
- // ██████╗ ███████╗████████╗██╗ ██╗██████╗ ███╗ ██╗
- // ██╔══██╗██╔════╝╚══██╔══╝██║ ██║██╔══██╗████╗ ██║
- // ██████╔╝█████╗ ██║ ██║ ██║██████╔╝██╔██╗ ██║
- // ██╔══██╗██╔══╝ ██║ ██║ ██║██╔══██╗██║╚██╗██║
- // ██║ ██║███████╗ ██║ ╚██████╔╝██║ ██║██║ ╚████║
- // ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝
- //
- // ██████╗ ███████╗███████╗███████╗██████╗ ██████╗ ███████╗██████╗
- // ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗██╔══██╗██╔════╝██╔══██╗
- // ██║ ██║█████╗ █████╗ █████╗ ██████╔╝██████╔╝█████╗ ██║ ██║
- // ██║ ██║██╔══╝ ██╔══╝ ██╔══╝ ██╔══██╗██╔══██╗██╔══╝ ██║ ██║
- // ██████╔╝███████╗██║ ███████╗██║ ██║██║ ██║███████╗██████╔╝
- // ╚═════╝ ╚══════╝╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═════╝
- //
- // Return deferred object
- return π;
- };
|