我尝试使用节点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
时创建的promise的triggerAsyncId是Promise.resolve()调用的executionAsyncId。这是在当前异步堆栈之外创建的,是一个单独的上下文。我无法将next
函数异步上下文与其创建的上下文结合起来。
答案 0 :(得分:1)
我写了一个名为node-request-context的非常相似的软件包,其中包含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'));
});
});
});
答案 1 :(得分:0)
我找到了一个不完美的解决方案,但确实有效。使用Promise.all包装原始承诺将解析为正确的executionAsyncId。但它确实依赖于调用代码知道promises上下文。
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());
});
});
});
});