使用 async_hooks 跟踪上下文
Tracking context with async_hooks
我正在尝试使用节点 async_hooks 通过异步堆栈跟踪上下文。它适用于大多数情况,但是我发现这个用例我想不出如何解决:
service.js:
const asyncHooks = require('async_hooks');
class Service {
constructor() {
this.store = {};
this.hooks = asyncHooks.createHook({
init: (asyncId, type, triggerAsyncId) => {
if (this.store[triggerAsyncId]) {
this.store[asyncId] = this.store[triggerAsyncId];
}
},
destroy: (asyncId) => {
delete this.store[asyncId];
},
});
this.enable();
}
async run(fn) {
this.store[asyncHooks.executionAsyncId()] = {};
await fn();
}
set(key, value) {
this.store[asyncHooks.executionAsyncId()][key] = value;
}
get(key) {
const state = this.store[asyncHooks.executionAsyncId()];
if (state) {
return state[key];
} else {
return null;
}
}
enable() {
this.hooks.enable();
}
disable() {
this.hooks.disable();
}
}
module.exports = Service;
service.spec.js
const assert = require('assert');
const Service = require('./service');
describe('Service', () => {
let service;
afterEach(() => {
service.disable();
});
it('can handle promises created out of the execution stack', async () => {
service = new Service();
const p = Promise.resolve();
await service.run(async () => {
service.set('foo');
await p.then(() => {
assert.strictEqual('foo', service.get());
});
});
});
});
此测试用例将失败,因为调用 next
时创建的承诺的 triggerAsyncId 是 Promise.resolve() 调用的 executionAsyncId。它是在当前异步堆栈之外创建的,并且是一个单独的上下文。我看不出有什么方法可以将 next
函数异步上下文与其创建时的上下文结合起来。
我写了一个非常相似的包node-request-context with a blog post来解释它。
您没有为 foo
定义任何值,并且在没有任何键的情况下调用 service.get()
时您没有要求任何值。但我想那是你写问题时的一个小错误。
您指出的主要问题是 Promise.resolve
的位置。我同意,没有办法让它发挥作用。这正是您创建 run
函数的原因,因此您将捕获 executionAsyncId
并使用它跟踪您的代码。否则,您无法跟踪任何上下文。
你的代码只是为了测试,但如果你真的需要,你可以使用箭头函数作弊:
it('can handle promises created out of the execution stack', async () => {
service = new Service();
const p = () => Promise.resolve();
await service.run(async () => {
service.set('foo', 'bar');
await p().then(() => {
assert.strictEqual('bar', service.get('foo'));
});
});
});
我找到了一个解决方案,虽然不完美,但确实有效。用 Promise.all 包装原始承诺将解析为正确的 executionAsyncId。但它确实依赖于调用代码了解承诺上下文。
const assert = require('assert');
const Service = require('./service');
describe('Service', () => {
let service;
afterEach(() => {
service.disable();
});
it('can handle promises created out of the execution stack', async () => {
service = new Service();
const p = Promise.resolve();
await service.run(async () => {
service.set('foo');
await Promise.all([p]).then(() => {
assert.strictEqual('foo', service.get());
});
});
});
});
我正在尝试使用节点 async_hooks 通过异步堆栈跟踪上下文。它适用于大多数情况,但是我发现这个用例我想不出如何解决:
service.js:
const asyncHooks = require('async_hooks');
class Service {
constructor() {
this.store = {};
this.hooks = asyncHooks.createHook({
init: (asyncId, type, triggerAsyncId) => {
if (this.store[triggerAsyncId]) {
this.store[asyncId] = this.store[triggerAsyncId];
}
},
destroy: (asyncId) => {
delete this.store[asyncId];
},
});
this.enable();
}
async run(fn) {
this.store[asyncHooks.executionAsyncId()] = {};
await fn();
}
set(key, value) {
this.store[asyncHooks.executionAsyncId()][key] = value;
}
get(key) {
const state = this.store[asyncHooks.executionAsyncId()];
if (state) {
return state[key];
} else {
return null;
}
}
enable() {
this.hooks.enable();
}
disable() {
this.hooks.disable();
}
}
module.exports = Service;
service.spec.js
const assert = require('assert');
const Service = require('./service');
describe('Service', () => {
let service;
afterEach(() => {
service.disable();
});
it('can handle promises created out of the execution stack', async () => {
service = new Service();
const p = Promise.resolve();
await service.run(async () => {
service.set('foo');
await p.then(() => {
assert.strictEqual('foo', service.get());
});
});
});
});
此测试用例将失败,因为调用 next
时创建的承诺的 triggerAsyncId 是 Promise.resolve() 调用的 executionAsyncId。它是在当前异步堆栈之外创建的,并且是一个单独的上下文。我看不出有什么方法可以将 next
函数异步上下文与其创建时的上下文结合起来。
我写了一个非常相似的包node-request-context with a blog post来解释它。
您没有为 foo
定义任何值,并且在没有任何键的情况下调用 service.get()
时您没有要求任何值。但我想那是你写问题时的一个小错误。
您指出的主要问题是 Promise.resolve
的位置。我同意,没有办法让它发挥作用。这正是您创建 run
函数的原因,因此您将捕获 executionAsyncId
并使用它跟踪您的代码。否则,您无法跟踪任何上下文。
你的代码只是为了测试,但如果你真的需要,你可以使用箭头函数作弊:
it('can handle promises created out of the execution stack', async () => {
service = new Service();
const p = () => Promise.resolve();
await service.run(async () => {
service.set('foo', 'bar');
await p().then(() => {
assert.strictEqual('bar', service.get('foo'));
});
});
});
我找到了一个解决方案,虽然不完美,但确实有效。用 Promise.all 包装原始承诺将解析为正确的 executionAsyncId。但它确实依赖于调用代码了解承诺上下文。
const assert = require('assert');
const Service = require('./service');
describe('Service', () => {
let service;
afterEach(() => {
service.disable();
});
it('can handle promises created out of the execution stack', async () => {
service = new Service();
const p = Promise.resolve();
await service.run(async () => {
service.set('foo');
await Promise.all([p]).then(() => {
assert.strictEqual('foo', service.get());
});
});
});
});