(function () { /* Imports */ var Meteor = Package.meteor.Meteor; var _ = Package.underscore._; /* Package-scope variables */ var Hook; (function(){ //////////////////////////////////////////////////////////////////////////////////// // // // packages/callback-hook/hook.js // // // //////////////////////////////////////////////////////////////////////////////////// // // XXX This pattern is under development. Do not add more callsites // 1 // using this package for now. See: // 2 // https://meteor.hackpad.com/Design-proposal-Hooks-YxvgEW06q6f // 3 // // 4 // Encapsulates the pattern of registering callbacks on a hook. // 5 // // 6 // The `each` method of the hook calls its iterator function argument // 7 // with each registered callback. This allows the hook to // 8 // conditionally decide not to call the callback (if, for example, the // 9 // observed object has been closed or terminated). // 10 // // 11 // By default, callbacks are bound with `Meteor.bindEnvironment`, so they will be // called with the Meteor environment of the calling code that // 13 // registered the callback. Override by passing { bindEnvironment: false } // 14 // to the constructor. // 15 // // 16 // Registering a callback returns an object with a single `stop` // 17 // method which unregisters the callback. // 18 // // 19 // The code is careful to allow a callback to be safely unregistered // 20 // while the callbacks are being iterated over. // 21 // // 22 // If the hook is configured with the `exceptionHandler` option, the // 23 // handler will be called if a called callback throws an exception. // 24 // By default (if the exception handler doesn't itself throw an // 25 // exception, or if the iterator function doesn't return a falsy value // 26 // to terminate the calling of callbacks), the remaining callbacks // 27 // will still be called. // 28 // // 29 // Alternatively, the `debugPrintExceptions` option can be specified // 30 // as string describing the callback. On an exception the string and // 31 // the exception will be printed to the console log with // 32 // `Meteor._debug`, and the exception otherwise ignored. // 33 // // 34 // If an exception handler isn't specified, exceptions thrown in the // 35 // callback will propagate up to the iterator function, and will // 36 // terminate calling the remaining callbacks if not caught. // 37 // 38 Hook = function (options) { // 39 var self = this; // 40 options = options || {}; // 41 self.nextCallbackId = 0; // 42 self.callbacks = {}; // 43 // Whether to wrap callbacks with Meteor.bindEnvironment // 44 self.bindEnvironment = true; // 45 if (options.bindEnvironment === false) // 46 self.bindEnvironment = false; // 47 // 48 if (options.exceptionHandler) // 49 self.exceptionHandler = options.exceptionHandler; // 50 else if (options.debugPrintExceptions) { // 51 if (! _.isString(options.debugPrintExceptions)) // 52 throw new Error("Hook option debugPrintExceptions should be a string"); // 53 self.exceptionHandler = options.debugPrintExceptions; // 54 } // 55 }; // 56 // 57 _.extend(Hook.prototype, { // 58 register: function (callback) { // 59 var self = this; // 60 var exceptionHandler = self.exceptionHandler || function (exception) { // 61 // Note: this relies on the undocumented fact that if bindEnvironment's // 62 // onException throws, and you are invoking the callback either in the // 63 // browser or from within a Fiber in Node, the exception is propagated. // 64 throw exception; // 65 }; // 66 // 67 if (self.bindEnvironment) { // 68 callback = Meteor.bindEnvironment(callback, exceptionHandler); // 69 } else { // 70 callback = dontBindEnvironment(callback, exceptionHandler); // 71 } // 72 // 73 var id = self.nextCallbackId++; // 74 self.callbacks[id] = callback; // 75 // 76 return { // 77 stop: function () { // 78 delete self.callbacks[id]; // 79 } // 80 }; // 81 }, // 82 // 83 // For each registered callback, call the passed iterator function // 84 // with the callback. // 85 // // 86 // The iterator function can choose whether or not to call the // 87 // callback. (For example, it might not call the callback if the // 88 // observed object has been closed or terminated). // 89 // // 90 // The iteration is stopped if the iterator function returns a falsy // 91 // value or throws an exception. // 92 // // 93 each: function (iterator) { // 94 var self = this; // 95 // 96 // Invoking bindEnvironment'd callbacks outside of a Fiber in Node doesn't // 97 // run them to completion (and exceptions thrown from onException are not // 98 // propagated), so we need to be in a Fiber. // 99 Meteor._nodeCodeMustBeInFiber(); // 100 // 101 var ids = _.keys(self.callbacks); // 102 for (var i = 0; i < ids.length; ++i) { // 103 var id = ids[i]; // 104 // check to see if the callback was removed during iteration // 105 if (_.has(self.callbacks, id)) { // 106 var callback = self.callbacks[id]; // 107 // 108 if (! iterator(callback)) // 109 break; // 110 } // 111 } // 112 } // 113 }); // 114 // 115 // Copied from Meteor.bindEnvironment and removed all the env stuff. // 116 var dontBindEnvironment = function (func, onException, _this) { // 117 if (!onException || typeof(onException) === 'string') { // 118 var description = onException || "callback of async function"; // 119 onException = function (error) { // 120 Meteor._debug( // 121 "Exception in " + description + ":", // 122 error && error.stack || error // 123 ); // 124 }; // 125 } // 126 // 127 return function (/* arguments */) { // 128 var args = _.toArray(arguments); // 129 // 130 var runAndHandleExceptions = function () { // 131 try { // 132 var ret = func.apply(_this, args); // 133 } catch (e) { // 134 onException(e); // 135 } // 136 return ret; // 137 }; // 138 // 139 return runAndHandleExceptions(); // 140 }; // 141 }; // 142 // 143 //////////////////////////////////////////////////////////////////////////////////// }).call(this); /* Exports */ if (typeof Package === 'undefined') Package = {}; Package['callback-hook'] = { Hook: Hook }; })(); //# sourceMappingURL=callback-hook.js.map