Що таке циклічні залежності, де вони мешкають, та як їх вирішити

💡 Усі статті, обговорення, новини про Front-end — в одному місці. Приєднуйтесь до Front-end спільноти!

Інтродукція

В цьому дописі я хочу розповісти про те, що таке циклічні залежності, як їх знаходити та виправляти. Нещодавно мені трапилася така помилка, й мені довелося витратити щонайменше 1.5 години на її вирішення, так як вона не має очевидних логів та її не може попередити IDE.

Тож, що це таке

Уявіть, що файл A імпортує файл Б, а файл Б в свою чергу використовує якісь дані з файлу А. Це може здаватися досить сюреалістичною ситуацією, але вона може нерідко траплятися на великих проєктах, де багато модулів

Уявімо, що в нас є 3 файли: index.ts, a.ts, b.ts. Ми на нашому невеликому проєкті використовуємо ESM (EcmaScript Modules). Ви також можете запустити цей приклад в sandbox


Гортайте вбік, щоб подивитися всі зображення

Що тут станеться?

Як ми можемо побачити, в нас виникла помилка Cannot read properties of undefined. Досить незвично, правда? Вона зачасту виникає, коли ми намагаємося читати властивості з чогось, що є undefined. На кшталт:

const obj1 = undefined;
const someProperty = obj1.myProperty

В цьому випадку ми отримуємо одну й ту саму помилку. Але стоп, як таке може бути? Ми ж чітко бачимо властивість serviceA, що ж тоді не так? Навіть ваш редактор коду, скоріш за все, не підкаже, в чому полягає помилка. А справа полягає в тому, що ми викликаємо файли приблизно таким чином:

  1. index.ts
  2. a.ts
  3. b.ts
  4. a.ts

Тут компілятор думає так:

  1. А підтягни ка мені файл a.ts.
  2. Ого, тут в нас є й інший імпорт, відкладемо код нижче на потім. Йдемо до файлу b.ts
  3. В нас тут є ще один імпорт від файлу a.ts, але ми такого не знаємо! Тоді призначимо значення undefined
  4. Експортуємо нову константу aName, але ж наш serviceA має значення undefined! Викидаю помилку, що не можу прочитати властивість на undefined.

Як можна побачити, кроки 2 та 4 повторюються, й насправді можуть повторюватися до нескінченності, це й є циклічна залежність. Наша консоль навіть повністю не відображає логу, й в цьому і є велика проблема

Рішень до подібних циклічних залежностей є декілька, але вони не завжди з найпростіших:

  1. Можемо перенести константу serviceA до файлу b.ts
  2. Можемо перенести константу aName до файлу a.ts

Але суть більшості таких рішень, що ми маємо групувати контент за змістом, і не розпорошувати змінні, функції то тут, то там. Це є ключовим моментом

Сподіваюся я досить зрозуміло пояснив тему, й це врятує кому-небудь годинку-іншу часу :)

Слава Україні!!!

👍ПодобаєтьсяСподобалось1
До обраногоВ обраному0
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Або винести константу в окремий файл .const.ts

У Python я вирішував схоже за допомогою абстрактних інтерфейсів.

ServiceA залежав не від ServiceB, а від ServiceBInterface.

Був окремий модуль який підставляв ServiceB при ініціалізації ServiceA.

Не завжди було можливо, і тоді додавався в одному з модулів умовний експорт в кінці, а не на початку.

Але це лише відтягує проблему, ефекти стають дивнішими.

Підписатись на коментарі