Skip to content

Commit a293122

Browse files
authored
feat: support asyncLocalStorage (#1721)
pick from #1455 pick from #1718
1 parent d36e5f7 commit a293122

4 files changed

Lines changed: 89 additions & 2 deletions

File tree

.github/workflows/node.js.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313

1414
strategy:
1515
matrix:
16-
node-version: [12.x, 14.x, 16.x]
16+
node-version: [12.x, 14.x, 16.x, 18.x]
1717

1818
steps:
1919
- uses: actions/checkout@v2
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
2+
'use strict';
3+
4+
const request = require('supertest');
5+
const assert = require('assert');
6+
const Koa = require('../..');
7+
8+
describe('app.currentContext', () => {
9+
it('should throw error if AsyncLocalStorage not support', () => {
10+
if (require('async_hooks').AsyncLocalStorage) return;
11+
assert.throws(() => new Koa({ asyncLocalStorage: true }),
12+
/Requires node 12\.17\.0 or higher to enable asyncLocalStorage/);
13+
});
14+
15+
it('should get currentContext return context when asyncLocalStorage enable', async() => {
16+
if (!require('async_hooks').AsyncLocalStorage) return;
17+
18+
const app = new Koa({ asyncLocalStorage: true });
19+
20+
app.use(async ctx => {
21+
assert(ctx === app.currentContext);
22+
await new Promise(resolve => {
23+
setTimeout(() => {
24+
assert(ctx === app.currentContext);
25+
resolve();
26+
}, 1);
27+
});
28+
await new Promise(resolve => {
29+
assert(ctx === app.currentContext);
30+
setImmediate(() => {
31+
assert(ctx === app.currentContext);
32+
resolve();
33+
});
34+
});
35+
assert(ctx === app.currentContext);
36+
app.currentContext.body = 'ok';
37+
});
38+
39+
const requestServer = async() => {
40+
assert(app.currentContext === undefined);
41+
await request(app.callback()).get('/').expect('ok');
42+
assert(app.currentContext === undefined);
43+
};
44+
45+
await Promise.all([
46+
requestServer(),
47+
requestServer(),
48+
requestServer(),
49+
requestServer(),
50+
requestServer()
51+
]);
52+
});
53+
54+
it('should get currentContext return undefined when asyncLocalStorage disable', async() => {
55+
const app = new Koa();
56+
57+
app.use(async ctx => {
58+
assert(app.currentContext === undefined);
59+
ctx.body = 'ok';
60+
});
61+
62+
await request(app.callback()).get('/').expect('ok');
63+
});
64+
});

__tests__/application/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ const assert = require('assert');
66
const Koa = require('../..');
77

88
describe('app', () => {
9-
it('should handle socket errors', done => {
9+
// ignore test on Node.js v18
10+
(/^v18\./.test(process.version) ? it.skip : it)('should handle socket errors', done => {
1011
const app = new Koa();
1112

1213
app.use((ctx, next) => {

lib/application.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
const isGeneratorFunction = require('is-generator-function');
99
const debug = require('debug')('koa:application');
1010
const onFinished = require('on-finished');
11+
const assert = require('assert');
1112
const response = require('./response');
1213
const compose = require('koa-compose');
1314
const context = require('./context');
@@ -64,6 +65,12 @@ module.exports = class Application extends Emitter {
6465
if (util.inspect.custom) {
6566
this[util.inspect.custom] = this.inspect;
6667
}
68+
if (options.asyncLocalStorage) {
69+
const { AsyncLocalStorage } = require('async_hooks');
70+
assert(AsyncLocalStorage, 'Requires node 12.17.0 or higher to enable asyncLocalStorage');
71+
this.ctxStorage = new AsyncLocalStorage();
72+
this.use(createAsyncCtxStorage(this));
73+
}
6774
}
6875

6976
/**
@@ -153,6 +160,13 @@ module.exports = class Application extends Emitter {
153160
return handleRequest;
154161
}
155162

163+
/**
164+
* return currnect contenxt from async local storage
165+
*/
166+
get currentContext() {
167+
if (this.ctxStorage) return this.ctxStorage.getStore();
168+
}
169+
156170
/**
157171
* Handle request in callback.
158172
*
@@ -222,6 +236,14 @@ module.exports = class Application extends Emitter {
222236
}
223237
};
224238

239+
function createAsyncCtxStorage(app) {
240+
return async function asyncCtxStorage(ctx, next) {
241+
await app.ctxStorage.run(ctx, async() => {
242+
return await next();
243+
});
244+
};
245+
}
246+
225247
/**
226248
* Response helper.
227249
*/

0 commit comments

Comments
 (0)