UNPKG

8.46 kBMarkdownView Raw
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
5Continuation-local storage works like thread-local storage in threaded
6programming, but is based on chains of Node-style callbacks instead of threads.
7The standard Node convention of functions calling functions is very similar to
8something called ["continuation-passing style"][cps] in functional programming,
9and the name comes from the way this module allows you to set and get values
10that are scoped to the lifetime of these chains of function calls.
11
12Suppose you're writing a module that fetches a user and adds it to a session
13before calling a function passed in by a user to continue execution:
14
15```javascript
16// setup.js
17
18var createNamespace = require('continuation-local-storage').createNamespace;
19var session = createNamespace('my session');
20
21var db = require('./lib/db.js');
22
23function 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
34Later on in the process of turning that user's data into an HTML page, you call
35another function (maybe defined in another module entirely) that wants to fetch
36the value you set earlier:
37
38```javascript
39// send_response.js
40
41var getNamespace = require('continuation-local-storage').getNamespace;
42var session = getNamespace('my session');
43
44var render = require('./lib/render.js')
45
46function finish(response) {
47 var user = session.get('user');
48 render({user: user}).pipe(response);
49}
50```
51
52When you set values in continuation-local storage, those values are accessible
53until all functions called from the original function – synchronously or
54asynchronously – 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
57asynchronous functions that call native functions (such as those exported from
58the `fs`, `dns`, `zlib` and `crypto` modules).
59
60A 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
62use continuation-local storage. This API is designed to allow you extend the
63scope of a variable across a sequence of function calls, but with values
64specific to each sequence of calls.
65
66Values are grouped into namespaces, created with `createNamespace()`. Sets of
67function calls are grouped together by calling them within the function passed
68to `.run()` on the namespace object. Calls to `.run()` can be nested, and each
69nested context this creates has its own copy of the set of values from the
70parent context. When a function is making multiple asynchronous calls, this
71allows each child call to get, set, and pass along its own context without
72overwriting the parent's.
73
74A simple, annotated example of how this nesting behaves:
75
76```javascript
77var createNamespace = require('contination-local-storage').createNamespace;
78
79var writer = createNamespace('writer');
80writer.run(function () {
81 writer.set('value', 0);
82
83 requestHandler();
84});
85
86function 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
119Each application wanting to use continuation-local values should create its own
120namespace. Reading from (or, more significantly, writing to) namespaces that
121don't belong to you is a faux pas.
122
123## cls.getNamespace(name)
124
125* return: {Namespace}
126
127Look up an existing namespace.
128
129## cls.destroyNamespace(name)
130
131Dispose of an existing namespace. WARNING: be sure to dispose of any references
132to destroyed namespaces in your old code, as contexts associated with them will
133no longer be propagated.
134
135## cls.reset()
136
137Completely reset all continuation-local storage namespaces. WARNING: while this
138will stop the propagation of values in any existing namespaces, if there are
139remaining references to those namespaces in code, the associated storage will
140still be reachable, even though the associated state is no longer being updated.
141Make sure you clean up any references to destroyed namespaces yourself.
142
143## process.namespaces
144
145* return: dictionary of {Namespace} objects
146
147Continuation-local storage has a performance cost, and so it isn't enabled
148until the module is loaded for the first time. Once the module is loaded, the
149current set of namespaces is available in `process.namespaces`, so library code
150that wants to use continuation-local storage only when it's active should test
151for the existence of `process.namespaces`.
152
153## Class: Namespace
154
155Application-specific namespaces group values local to the set of functions
156whose 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
167Set a value on the current continuation context. Must be set within an active
168continuation chain started with `namespace.run()` or `namespace.bind()`.
169
170### namespace.get(key)
171
172* return: the requested value, or `undefined`
173
174Look up a value on the current continuation context. Recursively searches from
175the innermost to outermost nested continuation context for a value associated
176with 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
183Create a new context on which values can be set or read. Run all the functions
184that are called (either directly, or indirectly through asynchronous functions
185that take callbacks themselves) from the provided callback within the scope of
186that namespace. The new context is passed as an argument to the callback
187whne it's called.
188
189### namespace.bind(callback, [context])
190
191* return: a callback wrapped up in a context closure
192
193Bind a function to the specified namespace. Works analogously to
194`Function.bind()` or `domain.bind()`. If context is omitted, it will default to
195the currently active context in the namespace, or create a new context if none
196is currently defined.
197
198### namespace.bindEmitter(emitter)
199
200Bind an EventEmitter to a namespace. Operates similarly to `domain.add`, with a
201less generic name and the additional caveat that unlike domains, namespaces
202never implicitly bind EventEmitters to themselves when they're created within
203the context of an active namespace.
204
205The most likely time you'd want to use this is when you're using Express or
206Connect and want to make sure your middleware execution plays nice with CLS, or
207are doing other things with HTTP listeners:
208
209```javascript
210http.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
222Use this with `namespace.bind()`, if you want to have a fresh context at invocation time,
223as opposed to binding time:
224
225```javascript
226function doSomething(p) {
227 console.log("%s = %s", p, ns.get(p));
228}
229
230function bindLater(callback) {
231 return writer.bind(callback, writer.createContext());
232}
233
234setInterval(function () {
235 var bound = bindLater(doSomething);
236 bound('test');
237}, 100);
238```
239
240## context
241
242A 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