3

我正在尝试使用节点 async_hooks 通过异步堆栈跟踪上下文。它适用于大多数情况,但是我发现这个用例我想不出如何解决:

服务.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;

服务规范.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());
      });
    });
  });
});

这个测试用例会失败,因为调用时创建的nextPromise 的 triggerAsyncId 是 Promise.resolve() 调用的 executionAsyncId。它是在当前异步堆栈之外创建的,是一个单独的上下文。我看不出有任何方法可以将next函数异步上下文与创建它的上下文结合起来。

https://github.com/domarmstrong/async_hook_then_example

4

2 回答 2

2

我写了一个非常相似的包,叫做node-request-context并附有一篇博文来解释它。

在没有任何键的情况下调用时,您没有定义任何值,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'));
    });
  });
});
于 2017-12-26T13:02:04.500 回答
0

我找到了一个不完美的解决方案,但确实有效。用 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());
      });
    });
  });
});
于 2018-01-02T10:51:53.763 回答