UNPKG

5.17 kBJavaScriptView Raw
1'use strict';
2
3var assert = require('assert');
4var wrapEmitter = require('emitter-listener');
5
6/*
7 *
8 * CONSTANTS
9 *
10 */
11var CONTEXTS_SYMBOL = 'cls@contexts';
12var ERROR_SYMBOL = 'error@context';
13
14// load polyfill if native support is unavailable
15if (!process.addAsyncListener) require('async-listener');
16
17function Namespace(name) {
18 this.name = name;
19 // changed in 2.7: no default context
20 this.active = null;
21 this._set = [];
22 this.id = null;
23}
24
25Namespace.prototype.set = function (key, value) {
26 if (!this.active) {
27 throw new Error("No context available. ns.run() or ns.bind() must be called first.");
28 }
29
30 this.active[key] = value;
31 return value;
32};
33
34Namespace.prototype.get = function (key) {
35 if (!this.active) return undefined;
36
37 return this.active[key];
38};
39
40Namespace.prototype.createContext = function () {
41 return Object.create(this.active);
42};
43
44Namespace.prototype.run = function (fn) {
45 var context = this.createContext();
46 this.enter(context);
47 try {
48 fn(context);
49 return context;
50 }
51 catch (exception) {
52 if (exception) {
53 exception[ERROR_SYMBOL] = context;
54 }
55 throw exception;
56 }
57 finally {
58 // this.exit(context);
59 }
60};
61
62Namespace.prototype.runAndReturn = function (fn) {
63 var value;
64 this.run(function (context) {
65 value = fn(context);
66 });
67 return value;
68};
69
70Namespace.prototype.bind = function (fn, context) {
71 if (!context) {
72 if (!this.active) {
73 context = this.createContext();
74 }
75 else {
76 context = this.active;
77 }
78 }
79
80 var self = this;
81 return function () {
82 self.enter(context);
83 try {
84 return fn.apply(this, arguments);
85 }
86 catch (exception) {
87 if (exception) {
88 exception[ERROR_SYMBOL] = context;
89 }
90 throw exception;
91 }
92 finally {
93 self.exit(context);
94 }
95 };
96};
97
98Namespace.prototype.enter = function (context) {
99 assert.ok(context, "context must be provided for entering");
100
101 this._set.push(this.active);
102 this.active = context;
103};
104
105Namespace.prototype.exit = function (context) {
106 assert.ok(context, "context must be provided for exiting");
107
108 // Fast path for most exits that are at the top of the stack
109 if (this.active === context) {
110 assert.ok(this._set.length, "can't remove top context");
111 this.active = this._set.pop();
112 return;
113 }
114
115 // Fast search in the stack using lastIndexOf
116 var index = this._set.lastIndexOf(context);
117
118 assert.ok(index >= 0, "context not currently entered; can't exit");
119 assert.ok(index, "can't remove top context");
120
121 this._set.splice(index, 1);
122};
123
124Namespace.prototype.bindEmitter = function (emitter) {
125 assert.ok(emitter.on && emitter.addListener && emitter.emit, "can only bind real EEs");
126
127 var namespace = this;
128 var thisSymbol = 'context@' + this.name;
129
130 // Capture the context active at the time the emitter is bound.
131 function attach(listener) {
132 if (!listener) return;
133 if (!listener[CONTEXTS_SYMBOL]) listener[CONTEXTS_SYMBOL] = Object.create(null);
134
135 listener[CONTEXTS_SYMBOL][thisSymbol] = {
136 namespace : namespace,
137 context : namespace.active
138 };
139 }
140
141 // At emit time, bind the listener within the correct context.
142 function bind(unwrapped) {
143 if (!(unwrapped && unwrapped[CONTEXTS_SYMBOL])) return unwrapped;
144
145 var wrapped = unwrapped;
146 var contexts = unwrapped[CONTEXTS_SYMBOL];
147 Object.keys(contexts).forEach(function (name) {
148 var thunk = contexts[name];
149 wrapped = thunk.namespace.bind(wrapped, thunk.context);
150 });
151 return wrapped;
152 }
153
154 wrapEmitter(emitter, attach, bind);
155};
156
157/**
158 * If an error comes out of a namespace, it will have a context attached to it.
159 * This function knows how to find it.
160 *
161 * @param {Error} exception Possibly annotated error.
162 */
163Namespace.prototype.fromException = function (exception) {
164 return exception[ERROR_SYMBOL];
165};
166
167function get(name) {
168 return process.namespaces[name];
169}
170
171function create(name) {
172 assert.ok(name, "namespace must be given a name!");
173
174 var namespace = new Namespace(name);
175 namespace.id = process.addAsyncListener({
176 create : function () { return namespace.active; },
177 before : function (context, storage) { if (storage) namespace.enter(storage); },
178 after : function (context, storage) { if (storage) namespace.exit(storage); },
179 error : function (storage) { if (storage) namespace.exit(storage); }
180 });
181
182 process.namespaces[name] = namespace;
183 return namespace;
184}
185
186function destroy(name) {
187 var namespace = get(name);
188
189 assert.ok(namespace, "can't delete nonexistent namespace!");
190 assert.ok(namespace.id, "don't assign to process.namespaces directly!");
191
192 process.removeAsyncListener(namespace.id);
193 process.namespaces[name] = null;
194}
195
196function reset() {
197 // must unregister async listeners
198 if (process.namespaces) {
199 Object.keys(process.namespaces).forEach(function (name) {
200 destroy(name);
201 });
202 }
203 process.namespaces = Object.create(null);
204}
205if (!process.namespaces) reset(); // call immediately to set up
206
207module.exports = {
208 getNamespace : get,
209 createNamespace : create,
210 destroyNamespace : destroy,
211 reset : reset
212};