-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathindex.js
163 lines (149 loc) · 5.08 KB
/
index.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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/*
* index.js: Simple pattern for deferable events, when you want an action to be interruptable
*
* action - string
* args... - anything
* performFn - once all "before" deferences are done call this function,
* then call all "after" deferences.
* onFinish - once all "after" deferences are done call this function.
*
*/
module.exports = Understudy;
module.exports.Understudy = Understudy;
function Understudy() {
this.perform = performer(false);
this.waterfall = performer(true);
this.after = registrar('_after_interceptors');
this.before = registrar('_before_interceptors');
this._before_interceptors = null;
this._after_interceptors = null;
return this;
}
function registrar(property) {
return function (action, callback) {
if (typeof action === 'string') {
if (typeof callback === 'function') {
this[property] || (this[property] = { '*': [] });
this[property][action] || (this[property][action] = []);
var interceptors = this[property][action];
interceptors[interceptors.length] = callback;
return this;
}
else {
throw new Error('callback must be a function');
}
}
throw new Error('event must be a string');
}
}
function performer(waterfall) {
return function perform(action /* , args..., performFn, callback*/) {
if (typeof action !== 'string') throw new Error('event must be a string');
var callback = arguments[arguments.length - 1];
var performFn = arguments[arguments.length - 2];
var slice = -2;
if (typeof performFn !== 'function') {
if (typeof callback !== 'function') {
throw new Error('performFn and callback must be a function');
}
performFn = callback;
callback = null;
slice = -1;
}
//
// Get "arguments" Array and set first to null to indicate
// to nextInterceptor that there is no error.
//
var args = Array.prototype.slice.call(arguments, 0, slice);
args[0] = null;
//
// This is called in multiple temporal localities, put into a function instead of inline
// minor speed loss for more maintainability
//
function iterate(self, interceptors, args, after) {
if (!interceptors || !interceptors.length) {
after.apply(self, args);
return;
}
var i = 0;
var len = interceptors.length;
if (!len) {
after.apply(self, args);
return;
}
function nextInterceptor() {
if (i === len) {
i++;
after.apply(self, arguments);
}
else if (i < len) {
var used = false;
var interceptor = interceptors[i++];
interceptor.apply(self, Array.prototype.slice.call(arguments, 1).concat(function next(err) {
//
// Do not allow multiple continuations
//
if (used) { return; }
used = true;
if (!err || !callback) {
nextInterceptor.apply(null, waterfall ? arguments : args);
} else {
after.call(self, err);
}
}));
}
}
nextInterceptor.apply(null, args);
}
//
// Remark (jcrugzz): Is this the most optimized way to do this?
//
function executePerform(err) {
var self = this;
if (err && callback) {
callback.call(this, err);
} else {
//
// Remark (indexzero): Should we console.warn if `arguments.length > 1` here?
//
performFn.call(this, function afterPerform(err) {
var performArgs, afterArgs;
if (err && callback) {
callback.call(self, err);
} else {
//
// Specifically for the `after` hooks, we call it with the result of
// the perform function to be able to do any modifications to the
// result of the work!
//
performArgs = Array.prototype.slice.call(arguments);
//
// If we are waterfalling we need to pass the performArgs
//
afterArgs = waterfall ? performArgs : args;
iterate(self, self._after_interceptors && self._after_interceptors['*'].concat(self._after_interceptors[action]), afterArgs, function (err) {
if (err && callback) {
callback.call(self, err);
} else if (callback) {
//
// Just to be sure we dont get javascript mad with object
// references, we check the length of the arguments to see if
// there is one, if not we default to whatever the performArgs.
// Remark: I want to note here that each c
//
return arguments.length > 1 && waterfall
? callback.apply(self, arguments)
: callback.apply(self, performArgs);
}
});
}
})
}
}
//
// Special flag for `isAfter`
//
iterate(this, this._before_interceptors && this._before_interceptors['*'].concat(this._before_interceptors[action]), args, executePerform);
return this;
}
}