Можливості проміжних обробників
У посібнику ми представили проміжні обробники як стек функцій. Хоча ви можете використовувати проміжні обробники у такий лінійний спосіб, називати їх просто стеком — це спрощення.
Проміжні обробники у grammY
Зазвичай ви бачите наступний шаблон.
const bot = new Bot("");
bot.use(/* ... */);
bot.use(/* ... */);
bot.on(/* ... */);
bot.on(/* ... */);
bot.on(/* ... */);
bot.start();
2
3
4
5
6
7
8
9
10
Виглядає дуже схоже на стек, за винятком того, що за кулісами це насправді дерево. Серцем цієї функціональсті є клас Composer
(довідка), який будує це дерево.
Насамперед кожен екземпляр Bot
є екземпляром Composer
. Він є підкласом, тому class Bot extends Composer
.
Крім того, ви повинні знати, що кожен окремий метод Composer
внутрішньо викликає use
. Наприклад, filter
просто викликає use
з деяким розгалуженним проміжним обробником, тоді як on
просто знову викликає filter
з деякою предикатною функцією, яка відповідає оновленням відповідного запиту фільтрування. Тому наразі ми можемо обмежитися розглядом use
, решта методів походить саме з нього.
Тепер ми маємо трохи зануритися в деталі того, що Composer
робить з вашими викликами use
, і чим він відрізняється від інших систем проміжних обробників. Різниця може здатися незначною, але зачекайте до наступного підрозділу, щоб дізнатися, чому вона має значні наслідки.
Збільшення можливостей Composer
Ви можете встановити додаткові проміжні обробники на екземпляр Composer
навіть після того, як десь вже встановили сам Composer
.
const bot = new Bot(""); // підклас класу `Composer`
const composer = new Composer();
bot.use(composer);
// Вони виконуватимуться:
composer.use(/* A */);
composer.use(/* B */);
composer.use(/* C */);
2
3
4
5
6
7
8
9
A
, B
і C
будуть виконані. Це означає, що коли ви встановили екземпляр Composer
, ви все ще можете викликати use
на ньому, і ці проміжні обробники теж буде виконано. У цьому немає нічого вражаючого, але це вже головна відмінність від популярних конкуруючих фреймворків, які просто ігнорують такі операції.
Напевно вам цікаво, де саме тут використовується структура дерева. Давайте розглянемо цей фрагмент:
const composer = new Composer();
composer.use(/* A */);
composer.use(/* B */).use(/* C */);
composer.use(/* D */).use(/* E */).use(/* F */).use(/* G */);
composer.use(/* H */).use(/* I */);
composer.use(/* J */).use(/* K */).use(/* L */);
2
3
4
5
6
7
Ви бачите це?
Як ви можете здогадатися, всі проміжні обробники буде запущено в порядку від A
до L
.
Інші бібліотеки внутрішньо зрівнюють цей код, щоб він був еквівалентним composer
і так далі. Навпаки, grammY зберігає вказане вами дерево: один кореневий вузол composer
має пʼять дочірніх елементів: A
, B
, D
, H
, J
, а дочірній B
має ще один дочірній елемент: C
. Кожне оновлення буде обходити це дерево в глибину, отже фактично проходячи від A
до L
у лінійному порядку, подібно до того, як це відбувається в інших системах.
Це стає можливим завдяки створенню нового екземпляра Composer
кожного разу, коли ви викликаєте use
, який у свою чергу буде розширено, як пояснено вище.
Поєднання викликів use
Якби ми використовували лише use
, це було б не надто корисним. Стає цікавіше, як тільки в гру вступає, наприклад, filter
.
Тільки погляньте:
const composer = new Composer();
composer.filter(/* 1 */, /* A */).use(/* B */)
composer.filter(/* 2 */).use(/* C */, /* D */)
2
3
4
5
У 3-му рядку ми реєструємо A
за предикатною функцією 1
. A
буде виконуватися лише для оновлень, які відповідають умові 1
. Однак filter
повертає екземпляр Composer
, який ми доповнюємо викликом use
у 3-му рядку, тому B
теж знаходиться за предикатною функцією 1
, навіть якщо його встановлено вже в іншому виклику use
.
5-й рядок еквівалентний 3-му рядку у тому відношенні, що і C
, і D
виконуватимуться, лише якщо виконується 2
.
Памʼятаєте, як виклики bot
можна обʼєднувати, щоб обʼєднати запити фільтрування за допомогою логічної операції І? Уявіть це:
const composer = new Composer();
composer.filter(/* 1 */).filter(/* 2 */).use(/* A */);
2
3
2
перевірятиметься, лише якщо виконується 1
, а A
запускатиметься, лише якщо виконується 2
, отже й 1
.
Перегляньте розділ про поєднання кількох запитів фільтрування зі своїми новими знаннями та відчуйте свою нову силу.
Особливим випадком тут є fork
, оскільки він запускає два конкурентних обчислення, тобто які можуть чергуватися в event loop. Замість повернення екземпляра Composer
, створеного базовим викликом use
, він повертає Composer
, який відображає розгалужене обчислення. Це дозволяє створювати короткі шаблони: наприклад, bot
. A
виконуватиметься на паралельний гілці обчислень.