1 | [![NPM](https://nodei.co/npm/continuation-local-storage.png?downloads=true&stars=true)](https://nodei.co/npm/continuation-local-storage/)
|
2 |
|
3 | # Continuation-Local Storage
|
4 |
|
5 | Continuation-local storage works like thread-local storage in threaded
|
6 | programming, but is based on chains of Node-style callbacks instead of threads.
|
7 | The standard Node convention of functions calling functions is very similar to
|
8 | something called ["continuation-passing style"][cps] in functional programming,
|
9 | and the name comes from the way this module allows you to set and get values
|
10 | that are scoped to the lifetime of these chains of function calls.
|
11 |
|
12 | Suppose you're writing a module that fetches a user and adds it to a session
|
13 | before calling a function passed in by a user to continue execution:
|
14 |
|
15 | ```javascript
|
16 | // setup.js
|
17 |
|
18 | var createNamespace = require('continuation-local-storage').createNamespace;
|
19 | var session = createNamespace('my session');
|
20 |
|
21 | var db = require('./lib/db.js');
|
22 |
|
23 | function start(options, next) {
|
24 | db.fetchUserById(options.id, function (error, user) {
|
25 | if (error) return next(error);
|
26 |
|
27 | session.set('user', user);
|
28 |
|
29 | next();
|
30 | });
|
31 | }
|
32 | ```
|
33 |
|
34 | Later on in the process of turning that user's data into an HTML page, you call
|
35 | another function (maybe defined in another module entirely) that wants to fetch
|
36 | the value you set earlier:
|
37 |
|
38 | ```javascript
|
39 | // send_response.js
|
40 |
|
41 | var getNamespace = require('continuation-local-storage').getNamespace;
|
42 | var session = getNamespace('my session');
|
43 |
|
44 | var render = require('./lib/render.js')
|
45 |
|
46 | function finish(response) {
|
47 | var user = session.get('user');
|
48 | render({user: user}).pipe(response);
|
49 | }
|
50 | ```
|
51 |
|
52 | When you set values in continuation-local storage, those values are accessible
|
53 | until all functions called from the original function – synchronously or
|
54 | asynchronously – have finished executing. This includes callbacks passed to
|
55 | `process.nextTick` and the [timer functions][] ([setImmediate][],
|
56 | [setTimeout][], and [setInterval][]), as well as callbacks passed to
|
57 | asynchronous functions that call native functions (such as those exported from
|
58 | the `fs`, `dns`, `zlib` and `crypto` modules).
|
59 |
|
60 | A simple rule of thumb is anywhere where you might have set a property on the
|
61 | `request` or `response` objects in an HTTP handler, you can (and should) now
|
62 | use continuation-local storage. This API is designed to allow you extend the
|
63 | scope of a variable across a sequence of function calls, but with values
|
64 | specific to each sequence of calls.
|
65 |
|
66 | Values are grouped into namespaces, created with `createNamespace()`. Sets of
|
67 | function calls are grouped together by calling them within the function passed
|
68 | to `.run()` on the namespace object. Calls to `.run()` can be nested, and each
|
69 | nested context this creates has its own copy of the set of values from the
|
70 | parent context. When a function is making multiple asynchronous calls, this
|
71 | allows each child call to get, set, and pass along its own context without
|
72 | overwriting the parent's.
|
73 |
|
74 | A simple, annotated example of how this nesting behaves:
|
75 |
|
76 | ```javascript
|
77 | var createNamespace = require('contination-local-storage').createNamespace;
|
78 |
|
79 | var writer = createNamespace('writer');
|
80 | writer.run(function () {
|
81 | writer.set('value', 0);
|
82 |
|
83 | requestHandler();
|
84 | });
|
85 |
|
86 | function requestHandler() {
|
87 | writer.run(function(outer) {
|
88 | // writer.get('value') returns 0
|
89 | // outer.value is 0
|
90 | writer.set('value', 1);
|
91 | // writer.get('value') returns 1
|
92 | // outer.value is 1
|
93 | process.nextTick(function() {
|
94 | // writer.get('value') returns 1
|
95 | // outer.value is 1
|
96 | writer.run(function(inner) {
|
97 | // writer.get('value') returns 1
|
98 | // outer.value is 1
|
99 | // inner.value is 1
|
100 | writer.set('value', 2);
|
101 | // writer.get('value') returns 2
|
102 | // outer.value is 1
|
103 | // inner.value is 2
|
104 | });
|
105 | });
|
106 | });
|
107 |
|
108 | setTimeout(function() {
|
109 | // runs with the default context, because nested contexts have ended
|
110 | console.log(writer.get('value')); // prints 0
|
111 | }, 1000);
|
112 | }
|
113 | ```
|
114 |
|
115 | ## cls.createNamespace(name)
|
116 |
|
117 | * return: {Namespace}
|
118 |
|
119 | Each application wanting to use continuation-local values should create its own
|
120 | namespace. Reading from (or, more significantly, writing to) namespaces that
|
121 | don't belong to you is a faux pas.
|
122 |
|
123 | ## cls.getNamespace(name)
|
124 |
|
125 | * return: {Namespace}
|
126 |
|
127 | Look up an existing namespace.
|
128 |
|
129 | ## cls.destroyNamespace(name)
|
130 |
|
131 | Dispose of an existing namespace. WARNING: be sure to dispose of any references
|
132 | to destroyed namespaces in your old code, as contexts associated with them will
|
133 | no longer be propagated.
|
134 |
|
135 | ## cls.reset()
|
136 |
|
137 | Completely reset all continuation-local storage namespaces. WARNING: while this
|
138 | will stop the propagation of values in any existing namespaces, if there are
|
139 | remaining references to those namespaces in code, the associated storage will
|
140 | still be reachable, even though the associated state is no longer being updated.
|
141 | Make sure you clean up any references to destroyed namespaces yourself.
|
142 |
|
143 | ## process.namespaces
|
144 |
|
145 | * return: dictionary of {Namespace} objects
|
146 |
|
147 | Continuation-local storage has a performance cost, and so it isn't enabled
|
148 | until the module is loaded for the first time. Once the module is loaded, the
|
149 | current set of namespaces is available in `process.namespaces`, so library code
|
150 | that wants to use continuation-local storage only when it's active should test
|
151 | for the existence of `process.namespaces`.
|
152 |
|
153 | ## Class: Namespace
|
154 |
|
155 | Application-specific namespaces group values local to the set of functions
|
156 | whose calls originate from a callback passed to `namespace.run()` or
|
157 | `namespace.bind()`.
|
158 |
|
159 | ### namespace.active
|
160 |
|
161 | * return: the currently active context on a namespace
|
162 |
|
163 | ### namespace.set(key, value)
|
164 |
|
165 | * return: `value`
|
166 |
|
167 | Set a value on the current continuation context. Must be set within an active
|
168 | continuation chain started with `namespace.run()` or `namespace.bind()`.
|
169 |
|
170 | ### namespace.get(key)
|
171 |
|
172 | * return: the requested value, or `undefined`
|
173 |
|
174 | Look up a value on the current continuation context. Recursively searches from
|
175 | the innermost to outermost nested continuation context for a value associated
|
176 | with a given key. Must be set within an active continuation chain started with
|
177 | `namespace.run()` or `namespace.bind()`.
|
178 |
|
179 | ### namespace.run(callback)
|
180 |
|
181 | * return: the context associated with that callback
|
182 |
|
183 | Create a new context on which values can be set or read. Run all the functions
|
184 | that are called (either directly, or indirectly through asynchronous functions
|
185 | that take callbacks themselves) from the provided callback within the scope of
|
186 | that namespace. The new context is passed as an argument to the callback
|
187 | whne it's called.
|
188 |
|
189 | ### namespace.bind(callback, [context])
|
190 |
|
191 | * return: a callback wrapped up in a context closure
|
192 |
|
193 | Bind a function to the specified namespace. Works analogously to
|
194 | `Function.bind()` or `domain.bind()`. If context is omitted, it will default to
|
195 | the currently active context in the namespace, or create a new context if none
|
196 | is currently defined.
|
197 |
|
198 | ### namespace.bindEmitter(emitter)
|
199 |
|
200 | Bind an EventEmitter to a namespace. Operates similarly to `domain.add`, with a
|
201 | less generic name and the additional caveat that unlike domains, namespaces
|
202 | never implicitly bind EventEmitters to themselves when they're created within
|
203 | the context of an active namespace.
|
204 |
|
205 | The most likely time you'd want to use this is when you're using Express or
|
206 | Connect and want to make sure your middleware execution plays nice with CLS, or
|
207 | are doing other things with HTTP listeners:
|
208 |
|
209 | ```javascript
|
210 | http.createServer(function (req, res) {
|
211 | writer.add(req);
|
212 | writer.add(res);
|
213 |
|
214 | // do other stuff, some of which is asynchronous
|
215 | });
|
216 | ```
|
217 |
|
218 | ### namespace.createContext()
|
219 |
|
220 | * return: a context cloned from the currently active context
|
221 |
|
222 | Use this with `namespace.bind()`, if you want to have a fresh context at invocation time,
|
223 | as opposed to binding time:
|
224 |
|
225 | ```javascript
|
226 | function doSomething(p) {
|
227 | console.log("%s = %s", p, ns.get(p));
|
228 | }
|
229 |
|
230 | function bindLater(callback) {
|
231 | return writer.bind(callback, writer.createContext());
|
232 | }
|
233 |
|
234 | setInterval(function () {
|
235 | var bound = bindLater(doSomething);
|
236 | bound('test');
|
237 | }, 100);
|
238 | ```
|
239 |
|
240 | ## context
|
241 |
|
242 | A context is a plain object created using the enclosing context as its prototype.
|
243 |
|
244 | [timer functions]: timers.html
|
245 | [setImmediate]: timers.html#timers_setimmediate_callback_arg
|
246 | [setTimeout]: timers.html#timers_settimeout_callback_delay_arg
|
247 | [setInterval]: timers.html#timers_setinterval_callback_delay_arg
|
248 | [cps]: http://en.wikipedia.org/wiki/Continuation-passing_style
|