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