@midwayjs/async-hooks-context-manager
Version:
midway async hooks context manager
140 lines • 4.8 kB
JavaScript
"use strict";
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.AsyncHooksContextManager = void 0;
const core_1 = require("@midwayjs/core");
const asyncHooks = require("async_hooks");
class AsyncHooksContextManager {
constructor() {
this._contexts = new Map();
this._stack = [];
this._asyncHook = asyncHooks.createHook({
init: this._init.bind(this),
before: this._before.bind(this),
after: this._after.bind(this),
destroy: this._destroy.bind(this),
promiseResolve: this._destroy.bind(this),
});
}
active() {
var _a;
return (_a = this._stack[this._stack.length - 1]) !== null && _a !== void 0 ? _a : core_1.ASYNC_ROOT_CONTEXT;
}
with(context, fn, thisArg, ...args) {
this._enterContext(context);
try {
return fn.call(thisArg, ...args);
}
finally {
this._exitContext();
}
}
enable() {
this._asyncHook.enable();
return this;
}
disable() {
this._asyncHook.disable();
this._contexts.clear();
this._stack = [];
return this;
}
/**
* Init hook will be called when userland create a async context, setting the
* context as the current one if it exist.
* @param uid id of the async context
* @param type the resource type
*/
_init(uid, type) {
// ignore TIMERWRAP as they combine timers with same timeout which can lead to
// false context propagation. TIMERWRAP has been removed in node 11
// every timer has it's own `Timeout` resource anyway which is used to propagete
// context.
if (type === 'TIMERWRAP')
return;
const context = this._stack[this._stack.length - 1];
if (context !== undefined) {
this._contexts.set(uid, context);
}
}
/**
* Destroy hook will be called when a given context is no longer used so we can
* remove its attached context.
* @param uid uid of the async context
*/
_destroy(uid) {
this._contexts.delete(uid);
}
/**
* Before hook is called just before executing a async context.
* @param uid uid of the async context
*/
_before(uid) {
const context = this._contexts.get(uid);
if (context !== undefined) {
this._enterContext(context);
}
}
/**
* After hook is called just after completing the execution of a async context.
*/
_after() {
this._exitContext();
}
/**
* Set the given context as active
*/
_enterContext(context) {
this._stack.push(context);
}
/**
* Remove the context at the root of the stack
*/
_exitContext() {
this._stack.pop();
}
/**
* Binds a the certain context or the active one to the target function and then returns the target
* @param context A context (span) to be bind to target
* @param target a function. When target or one of its callbacks is called,
* the provided context will be used as the active context for the duration of the call.
*/
bind(context, target) {
if (typeof target === 'function') {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const manager = this;
const contextWrapper = function (...args) {
return manager.with(context, () => target.apply(this, args));
};
Object.defineProperty(contextWrapper, 'length', {
enumerable: false,
configurable: true,
writable: false,
value: target.length,
});
/**
* It isn't possible to tell Typescript that contextWrapper is the same as T
* so we forced to cast as any here.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return contextWrapper;
}
return target;
}
}
exports.AsyncHooksContextManager = AsyncHooksContextManager;
//# sourceMappingURL=asyncHooksContextManager.js.map