Короче
При использовании AsyncIterator
память резко увеличивается. Он сбрасывается после завершения итерации.
x
в `для await (x of y) не освобождается, пока итерация не будет завершена. Также каждое Обещание, ожидаемое внутри цикла for, не освобождается.
Я пришел к выводу, что Сборщик мусора не может перехватить содержимое Итерации, поскольку Обещания, сгенерированные AsyncIterator
, будут полностью разрешены только после того, какИтерация завершена. Я думаю, что это может быть ошибка.
Обходное решение Repro
В качестве обходного пути для освобождения содержимого анализатора мы инкапсулируем Result в легкий контейнер. Затем мы освобождаем содержимое, поэтому в памяти остается только сам контейнер. data
Объект не может быть освобожден, даже если вы используете ту же технику для его инкапсуляции - так что, похоже, это имеет место при отладке по крайней мере.
const scraper = async ():Promise<void> => {
let browser = new BrowserTest();
for await (const data of browser){
let parser = new ParserTest();
let result = await parser.parse(data);
console.log(result);
/**
* This avoids memory leaks, due to a garbage collector bug
* of async iterators in js
*/
result.free();
}
}
class BrowserTest {
private i: number = 0;
private value: string = "";
public async next(): Promise<IteratorResult<string>> {
this.i += 1;
this.value = 'peter '.repeat(this.i);
return {
done: this.i > 1000,
value: this.value
}
}
public [Symbol.asyncIterator](): AsyncIterator<string> {
return this;
}
}
/**
* Result class for wrapping the result of the parser.
*/
class Result {
private result: string[] = [];
constructor(result: string[]){
this.setResult(result);
}
public setResult(result: string[]) {
this.result = result;
}
public getResult(): string[] {
return this.result;
}
public free(): void {
delete this.result;
}
}
class ParserTest {
public async parse(data: string): Promise<Result>{
let result = data.split(' ');
return new Result(result);
}
}
scraper())
Обходной путь в реальном контексте
В Repro-Solution не показано, что мы также пытаемся освободить сам результат итерации. Кажется, что это не имеет никакого эффекта, хотя (?).
public static async scrape<D,M>(scraper: IScraper<D,M>, callback: (data: DataPackage<Object,Object> | null) => Promise<void>) {
let browser = scraper.getBrowser();
let parser = scraper.getParser();
for await (const parserFragment of browser) {
const fragment = await parserFragment;
const json = await parser.parse(fragment);
await callback(json);
json.free();
fragment.free();
}
}
См .: https://github.com/demokratie-live/scapacra/blob/master/src/Scraper.ts Чтобы проверить с реальным приложением: https://github.com/demokratie-live/scapacra-bt (yarn dev ConferenceWeekDetail
)
Ссылки
Вывод
Мы нашлиВозможное решение для нас. Поэтому я закрываю этот выпуск. Отслеживание направлено на репо Node.js, чтобы исправить эту потенциальную ошибку
https://github.com/nodejs/node/issues/30298