blob: ceb4c13cc58215aa865be5516e141178e776aea3 [file] [edit]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
export class AsyncScope {
static scopes = new Set<AsyncScope>();
static abortSignal: AbortSignal|undefined;
private asyncStack: Array<{description?: string, frames: string[], messages: string[]}> = [];
get descriptions(): string[] {
return this.asyncStack.flatMap(({description, messages}) => {
const indentedMessages = messages.map(message => ` - ${message}`);
return (description || indentedMessages.length > 0) ? [description ?? '[no description]', ...indentedMessages] :
[];
});
}
get stack(): null|string[] {
if (this.asyncStack.length === 0) {
return null;
}
return this.asyncStack[this.asyncStack.length - 1].frames;
}
push(description?: string): string[] {
const stack = new Error().stack;
if (!stack) {
throw new Error('Could not get stack trace');
}
if (this.asyncStack.length === 0) {
AsyncScope.scopes.add(this);
}
const frames = stack.split('\n').filter(
value =>
!(value === 'Error' || value.includes('AsyncScope') || value.includes('runMicrotasks') ||
value.includes('processTicksAndRejections')));
const messages: string[] = [];
this.asyncStack.push({description, frames, messages});
return messages;
}
pop() {
this.asyncStack.pop();
if (this.asyncStack.length === 0) {
AsyncScope.scopes.delete(this);
}
}
async exec<T>(callable: (messages: string[]) => Promise<T>, description?: string) {
const messages = this.push(description);
try {
const result = await callable(messages);
return result;
} finally {
this.pop();
}
}
}