В постах про using мы разбирали, как освобождение ресурса привязывается к границам блока. Но у этого подхода есть предел: объявлений должно быть столько же, сколько ресурсов, и все они должны быть известны на момент написания кода. Для случаев, когда ресурсов заранее неизвестное количество, в стандарте есть DisposableStack и его асинхронный вариант AsyncDisposableStack.
Допустим, надо открыть файлы по списку путей и поработать со всеми сразу. Написать объявление в цикле не выйдет, потому что тело цикла — это отдельная область, и файл закроется на своей же итерации, а не в конце.
Стек решает это тем, что накапливает ресурсы по ходу дела, но сам при этом подчиняется тому же протоколу. То есть объявляется через using, и когда блок заканчивается, освобождает всё накопленное в обратном порядке.
await using stack = new AsyncDisposableStack();
const files = [];
for (const p of paths) {
const file = stack.use(await open(p));
files.push(file);
}
// работаем со всеми файлами
Метод use() кладёт объект в стек и возвращает его же, поэтому вызов удобно писать прямо вокруг выражения, которое ресурс создаёт, то есть так stack.use(await open(p)). В документации советуют не разносить получение ресурса и регистрацию на две строки, чтобы между ними не появился код, который может упасть. Ведь в этот момент стек ещё не знает про ресурс, а значит, не может его закрыть.
Также у стека есть метод adopt(). Он принимает значение и функцию для очистки, которая получит это значение аргументом:
stack.adopt(setInterval(tick, 1000), clearInterval);
Таким образом, стек оказывается универсальнее самого using. Для using объект обязан иметь [Symbol.dispose], а стеку достаточно передать функцию для закрытия. Поэтому стек может иметь смысл даже для единственного ресурса, если у того нет нужного протокола:
using stack = new DisposableStack();
stack.adopt(handler, h => emitter.off('data', h));
Допустим, надо открыть файлы по списку путей и поработать со всеми сразу. Написать объявление в цикле не выйдет, потому что тело цикла — это отдельная область, и файл закроется на своей же итерации, а не в конце.
Стек решает это тем, что накапливает ресурсы по ходу дела, но сам при этом подчиняется тому же протоколу. То есть объявляется через using, и когда блок заканчивается, освобождает всё накопленное в обратном порядке.
await using stack = new AsyncDisposableStack();
const files = [];
for (const p of paths) {
const file = stack.use(await open(p));
files.push(file);
}
// работаем со всеми файлами
Метод use() кладёт объект в стек и возвращает его же, поэтому вызов удобно писать прямо вокруг выражения, которое ресурс создаёт, то есть так stack.use(await open(p)). В документации советуют не разносить получение ресурса и регистрацию на две строки, чтобы между ними не появился код, который может упасть. Ведь в этот момент стек ещё не знает про ресурс, а значит, не может его закрыть.
Также у стека есть метод adopt(). Он принимает значение и функцию для очистки, которая получит это значение аргументом:
stack.adopt(setInterval(tick, 1000), clearInterval);
Таким образом, стек оказывается универсальнее самого using. Для using объект обязан иметь [Symbol.dispose], а стеку достаточно передать функцию для закрытия. Поэтому стек может иметь смысл даже для единственного ресурса, если у того нет нужного протокола:
using stack = new DisposableStack();
stack.adopt(handler, h => emitter.off('data', h));