<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/feeds/stylesheet.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
	
	
	<link href="https://blog.comandeer.pl/feeds/kategorie/refleksje.xml" rel="self" type="application/atom+xml"/>
	<link href="https://blog.comandeer.pl/kategorie/refleksje" rel="alternate" type="text/html"/>
	<updated>2026-04-25T11:06:04.890Z</updated>
	<id>https://blog.comandeer.pl/feeds/kategorie/refleksje.xml</id>
	<title type="html">Comandeerowy blog</title>
	
		<category term="Refleksje"/>
		<subtitle>Kategoria Refleksje</subtitle>
	
	
		
	
	
		<entry>
			<title type="html">Podsumowanie 2025</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/podsumowanie-2025" rel="alternate" type="text/html"/>
			<published>2025-12-31T22:15:00.000Z</published>
			<updated>2025-12-31T22:15:00.000Z</updated>
			<id>https://blog.comandeer.pl/podsumowanie-2025</id>
			
				<summary><![CDATA[Krótko o tym, co się wydarzyło w 2025 roku]]></summary>
			
			<content type="html"><![CDATA[<p>Kolejny rok minął… Nie powiem, był męczący i mało produktywny. A potem wpadłem na <em>iście genialny</em> pomysł i stwierdziłem, że zrobię <a href="https://blog.comandeer.pl/kategorie/adwent-2025/">kalendarz adwentowy</a>. I jakimś cudem dowiozłem! Mimo że byłem pewien, że się nie uda. 19 grudnia złapało mnie choróbsko i kalendarz był zagrożony, ale udało mi zebrać się do kupy i opublikować kolejny wpis 20 grudnia (co prawda o 17, zamiast o północy, ale hej – i tak się liczy!). Tym samym norma na blogu została wyrobiona – i to podwójnie! Ale jeśli ktoś się&nbsp;zastanawia, czy w przyszłym roku też będzie kalendarz adwentowy: nie, na pewno nie. Będzie za to <em>wieniec</em> adwentowy, czyli jeden wpis na każdą niedzielę adwentu.</p>
<p>Jeśli chodzi o rzeczy, z których jestem dumny: na tym blogu to zdecydowanie tekst <a href="https://blog.comandeer.pl/nullius-in-verba"><cite lang="la">Nullius in verba</cite></a> oraz <a href="https://blog.comandeer.pl/projekty/czasomierze/">projekt czasomierzy</a>. I tak, zdaję sobie sprawę, że jest ukończony gdzieś w połowie, ale styczeń i luty brzmią jak dobry czas, by go dokończyć. I ruszyć w końcu z kolejnym zapowiedzianym projektem, czyli <a href="https://blog.comandeer.pl/projekty/doco/">Doco</a>! Bo nie będę ukrywał, moje projekty w tym roku leżały odłogiem i nie miałem sił do nich zaglądnąć. Ale kalendarz trochę mnie rozruszał. A bardzo mi się to przyda, bo oprócz projektów jest przecież również WebKrytyk, który ostatnio też trochę się zakurzył. W tym roku pojawiły się na nim raptem 4 teksty, z których najbardziej podoba mi się ten o <a href="https://www.webkrytyk.pl/2025/05/15/wpadki-i-wypadki-20/" rel="noreferrer noopener">odpowiadających sobie doświadczeniach</a>. Nie można też pominąć faktu, że… <a href="https://www.webkrytyk.pl/2025/12/31/100-lat-czyli-sentymentalna-podroz-w-przeszlosc/#15-lat-minelo" rel="noreferrer noopener">WebKrytyk ma już 15 lat</a>. Yay…!</p>
<p>I to w sumie tyle. Szczęśliwego Nowego Roku 🍾!</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Nullius in verba</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/nullius-in-verba" rel="alternate" type="text/html"/>
			<published>2025-12-23T23:06:00.000Z</published>
			<updated>2025-12-23T23:06:00.000Z</updated>
			<id>https://blog.comandeer.pl/nullius-in-verba</id>
			
				<summary><![CDATA[A co, gdybyśmy traktowali webdev bardziej jak naukę?]]></summary>
			
			<content type="html"><![CDATA[<p>Osoby nieco dłużej śledzące moją internetową aktywność mogły zauważyć pewną zmianę mojego podejścia do tematów webdevowych. W mojej dawnej webkrytykowej twórczości, ale i w początkach istnienia tego bloga, o wiele więcej było, hm, <em>emocjonalnych</em> wstawek. Niczym dekadencki poeta z bujną i rozwichrzoną czupryną zdecydowanie stawiałem formę wyżej od treści. Obecnie moje podejście jest zdecydowanie bardziej <em>naukowe</em>. I to bynajmniej nie z powodu nagłego braku bujnej czupryny!</p>
<h2 id="podejście-naukowe"><a class="header-anchor" href="https://blog.comandeer.pl/nullius-in-verba#podejście-naukowe">Podejście naukowe</a></h2>
<p>Co to jednak znaczy, że traktuję webdev naukowo? Tak naprawdę wyróżniłbym tutaj trzy główne elementy:</p>
<ol>
<li>formę treści,</li>
<li>obecność źródeł,</li>
<li>empiryczność.</li>
</ol>
<h3 id="forma-treści"><a class="header-anchor" href="https://blog.comandeer.pl/nullius-in-verba#forma-treści">Forma treści</a></h3>
<p>Artykuły naukowe mają ściśle określoną strukturę. Bardzo często opisuje się ją <a href="https://en.wikipedia.org/wiki/IMRAD" rel="noreferrer noopener">akronimem IMRaD</a>:</p>
<ol>
<li><b lang="en">Introduction</b> (wprowadzenie) – wyjaśnienie celu badania, postawienie hipotezy,</li>
<li><b lang="en">Methods</b> (metody badawcze) – w jaki sposób badanie zostało wykonane, kto w nim uczestniczył,</li>
<li><b lang="en">Results</b> (wyniki) – omówienie wyników badania, sprawdzenie, czy wyniki zgadzają się&nbsp;z postawioną hipotezą,</li>
<li><b lang="en">Discussion</b> (polemika) – próba interpretacji wyników, porównanie ich z innymi badaniami z tego zakresu, zaproponowanie możliwych przyszłych kierunków badań.</li>
</ol>
<p>Tak tworzone artykuły powinny zachowywać jak największą obiektywność, skupiając się przede wszystkim na opisie faktów, z pominięciem opinii osób autorskich.</p>
<p>Ja nie jestem naukowcem i siłą mojego bloga jest jednak to, że umieszczam na nim materiały o webdevie okraszone <em>moją</em> opinią. Więc bardziej nazwałbym swoją radosną twórczość <dfn id="esej-hipertekstowy">esejami hipertekstowymi</dfn>. To nic innego jak <a href="https://pl.wikipedia.org/wiki/Esej" rel="noreferrer noopener">eseje</a>, które są opublikowane w formie hipertekstu. Czyli: mogą linkować do innych esejów lub bezpośrednio do źródeł, Równocześnie jednak – raz luźniej, raz ściślej – trzymam się struktury IMRaD. Weźmy taki <a href="https://blog.comandeer.pl/pudelko-z-ciasteczkami">artykuł o Cookie Store API</a>. Na samym początku jest wprowadzenie, w którym zaznaczam problem (<q>relacja Sieci z ciasteczkami od zawsze była skomplikowana</q>). Następnie następuje omówienie wyników badania (w tym wypadku: organoleptycznego sprawdzenia, jak się obsługuje ciasteczka w przeglądarkach). W przypadku artykułów z eksperymentów (jak np. <a href="https://blog.comandeer.pl/jednoplikowe-komponenty">jednoplikowych komponentów</a> czy <a href="https://blog.comandeer.pl/ascss">ASCSS-a</a>) pojawiają się&nbsp;też sekcje z metodami (opis inspiracji i narzędzi użytych do stworzenia danego projektu) oraz polemika (w jaki sposób go dalej rozwijać, czy moje pierwotne założenia udało się spełnić, itd.).</p>
<h3 id="źródła"><a class="header-anchor" href="https://blog.comandeer.pl/nullius-in-verba#źródła">Źródła</a></h3>
<p>Niemniej to nie forma jest głównym wyznacznikiem “naukowości”. Według mnie o wiele ważniejszym jest odwoływanie się do źródeł. W końcu szansa, że opisałem coś jako pierwszy w Internecie, jest niemalże zerowa. Nawet jeśli w swoim zakątku Sieci jestem faktycznie pierwszy, to przecież o jakimś standardzie sieciowym napisała przede mną choćby… osoba pisząca jego specyfikację. Nawet jeśli już wymyślę&nbsp;coś absolutnie swojego (jak <a href="https://blog.comandeer.pl/headings-first-principle"><span lang="en">Headings First Principle</span> (HFP)</a>), to przecież to nie istnieje w próżni. Żeby mogło powstać HFP, musiał powstać HTML, a w nim nagłówki. Niezbędnymi elementami były też wszelkie <a href="https://www.smashingmagazine.com/2022/07/article-section-elements-accessibility/" rel="noreferrer noopener">dobre praktyki wokół dzielenia treści stron WWW na sekcje</a> oraz <a href="https://webaim.org/projects/screenreadersurvey10/#finding" rel="noreferrer noopener">sposób nawigowania przy pomocy nagłówków przez osoby korzystające z czytników ekranowych</a>. Słowem: musiał istnieć cały skomplikowany ekosystem Sieci, żebym ja mógł do niego dodać swoją małą&nbsp;cegiełkę.</p>
<p>Chyba najdobitniej widać to w przypadku mojego, jak dotąd najambitniejszego, projektu, <a href="https://gwd.comandeer.pl/" rel="noreferrer noopener">GWD</a>. W każdym “rozdziale” na końcu znajdują&nbsp;się listy źródeł oraz dodatkowych materiałów. Nazbierało się ich <strong>około tysiąca</strong>. A i tak była to mocno okrojona lista. Zwłaszcza, że sporo tematów ostatecznie wypadło z tego eseju.</p>
<p>Niemniej samo odesłanie do źródeł to za mało. Prawdziwa praca polega na odpowiednim ich dobraniu. Bo można opisać np. element HTML <code>bdo</code> i odesłać do jakiejś strony-krzak czy <a href="https://www.webkrytyk.pl/2022/10/30/w3schools-com-runda-druga/" rel="noreferrer noopener">popularnej, acz słabej jakości witryny</a>. Zamiast tego wypada jednak dokładnie zapoznać się, do czego linkujemy, czy nie ma tam błędów merytorycznych, czy poruszone są wszystkie kwestie ważne poruszenia, itd. I tak jak w świecie nauki, w którym mamy tytuły godne zaufania (<a href="https://www.science.org/" rel="noreferrer noopener">Science</a>, <a href="https://www.nature.com/" rel="noreferrer noopener">Nature</a>), tak i w webdevie jedne źródła są bezpieczniejsze od innych. Najbezpieczniejsze są, rzecz jasna, specyfikacje i standardy. Nie ma pewniejszych źródeł od nich. Jeśli chcę opisać element <code>bdo</code>, to <a href="https://html.spec.whatwg.org/multipage/text-level-semantics.html#the-bdo-element" rel="noreferrer noopener">specyfikacja HTML</a> jest Kanonicznym Źródłem Prawdy™. Ale standardy często są pisane w sposób całkowicie hermetyczny i niezrozumiały dla dowolnej osoby, która nie czyta sobie RFC do poduszki. Wtedy warto sięgnąć po drugie najlepsze źródło – <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/bdo" rel="noreferrer noopener">dokumentację MDN</a>. Jeśli coś można by nazwać <q>oficjalną dokumentacją platformy sieciowej</q>, to byłoby to właśnie MDN.</p>
<p>Dobrej jakości źródeł jest oczywiście więcej – jak choćby <a href="https://www.smashingmagazine.com/" rel="noreferrer noopener">Smashing Magazine</a>, <a href="https://alistapart.com/" rel="noreferrer noopener">A List Apart</a> czy blogi znanych osób ze światka webdevu. I często na tych stronach, oderwanych od “oficjalnego obiegu”, dzieją się rzeczy ciekawsze. To w końcu na łamach A List Apart powstał termin <a href="https://alistapart.com/article/responsive-web-design/" rel="noreferrer noopener">Responsive Web Design</a>! Często też wyłuskać można na jakiejś stronie jakąś perełkę wśród całej reszty <em>meh</em> artykułów (hej, to zupełnie jak na moim blogu!). Tylko no właśnie – <em>wyłuskać</em>…</p>
<p>Sprawne posługiwanie się źródłami wymaga mimo wszystko wprawy i pewnego doświadczenia. Ja w swoim życiu przerobiłem już <em>tysiące</em> artykułów o webdevie i jestem w stanie oddzielić te faktycznie ciekawe od tych, które takie nie są. I właśnie tę umiejętność selekcji uważam za kluczową w bardziej naukowym podejściu do webdevu. Żeby ją zdobyć, to nie ma innej rady – trzeba spędzić swoje na czytaniu. Na początku będzie się czytać wszystko, jak leci, by z czasem odrzucać te najgorsze z najgorszych, aż w końcu nadejdzie moment, w którym się Po Prostu Wie™, co jest warte poświęcenia czasu, a co nie.</p>
<p>Niemniej z selekcją źródeł jest jeszcze jeden mały haczyk: ryzyko wpadnięcia w bańkę. Każda osoba ma jakieś poglądy i przekonania. Choćby tak błahe, jak nielubienie Reacta czy skłonność do wybierania mniej popularnych rozwiązań. I takie przekonania będą – bardziej lub mniej świadomie – wpływać na to, które źródła się dobiera. Nie sądzę, by dało się od tego całkowicie uciec, ale im bardziej się jest tego świadomym, tym mniejszy ma to wpływ na proces selekcji. Bo czasami warto wręcz sięgać po źródła, z którymi się nie zgadzamy – choćby po to, by <a href="https://gwd.comandeer.pl/progressive-enhancement/#krytyka" rel="noreferrer noopener">wejść&nbsp;z nimi w polemikę</a>.</p>
<h3 id="empiryczność"><a class="header-anchor" href="https://blog.comandeer.pl/nullius-in-verba#empiryczność">Empiryczność</a></h3>
<p>Wreszcie ostatni element, który widać w większości moich artykułów – zarówno tutaj, jak i <a href="https://www.webkrytyk.pl/2021/10/31/wpadki-i-wypadki-14/" rel="noreferrer noopener">na WebKrytyku</a>. Empiryczność, czyli mówiąc inaczej: eksperymentowanie. Kiedy opisuję nowe API, fajnie byłoby, gdybym przygotował choćby krótkie demko, w którym bym zademonstrował jego podstawowe ficzery. Tym prostym sposobem można być jak Longinus Podbipięta i <s>ściąć trzy gł</s> odhaczyć trzy rzeczy:</p>
<ol>
<li>pokazujemy osobom czytającym,&nbsp;że opisywane przez nas rzeczy faktycznie istnieją i działają,</li>
<li>tworzymy dla siebie samych z przyszłości źródło do linkowania, jeśli kiedyś wspomnimy znowu o tym API,</li>
<li>zapamiętujemy całą rzecz lepiej, niż gdybyśmy naskrobali jedynie teoretyczny opis.</li>
</ol>
<p>Poza tym – ostatecznie piszę ten blog dla przyjemności. A gdzie byłby cały fun z tego, gdybym się nie bawił kodem?</p>
<h2 id="tylko-po-co"><a class="header-anchor" href="https://blog.comandeer.pl/nullius-in-verba#tylko-po-co">Tylko po co?</a></h2>
<p>Odpowiedziałem już na pytanie, <q>jak</q> traktować webdev bardziej naukowo. Pora zatem pochylić się nad drugim, równie ważnym – jeśli nawet nie ważniejszym – pytaniem: <q>po co</q>?</p>
<p>Bo uważam, że tego wymaga uczciwość&nbsp;intelektualna. Webdev nie jest jakąś bliżej niezdefiniowaną sztuką okultystyczną (mimo że czasami tak się jawi!). Wbrew pozorom zdecydowana większość platformy sieciowej jest oparta na konkretnych standardach i specyfikacjach. Bo gdyby nie była, strony WWW działałyby w jednych przeglądarkach i nie działały w innych. <a href="https://thehistoryoftheweb.com/browser-wars/" rel="noreferrer noopener">Te czasy już przerabialiśmy</a> i to właśnie dzięki nim dzisiaj mamy otwarte standardy sieciowe. Jasne, nie jest idealnie, ale inicjatywy takie jak <a href="https://wpt.fyi/" rel="noreferrer noopener">Web Platform Tests</a> czy <a href="https://web.dev/blog/interop-2025" rel="noreferrer noopener">Interop</a> dają nadzieję, ze będzie lepiej. A skoro Sieć oparta jest na fundamentach, które są skodyfikowane (spisane “na papierze”, z przybitą pieczątką) i możliwe do empirycznej weryfikacji (w każdej chwili mogę odpalić przeglądarkę i sprawdzić, jak ten HTML czy CSS działają), to nie widzę powodu, by moje artykuły się do tego nie odwoływały.</p>
<p>Uczciwość intelektualna wymaga też, by istniała pewna ciągłość między tym, co tworzymy, a tym, co zostało stworzone. Tak jak artykuł naukowy nie może istnieć bez źródeł, tak trudno stworzyć artykuł o hipertekście bez… hipertekstu. W webdevie nie ma <a href="https://en.wikipedia.org/wiki/Self-made_man" rel="noreferrer noopener"><i lang="en">self-made men</i></a>, wszyscy budujemy na fundamentach postawionych przez innych. Nawet jeśli nasze budowanie polega na niezgadzaniu się z już istniejącymi dobrymi praktykami i proponowaniu czegoś nowego. Bo ostatecznie nie byłoby tej propozycji, gdyby nie ta praktyka! Żeby się z czymś nie zgadzać, trzeba najpierw zauważyć istnienie tego czegoś. Inaczej <a href="https://www.webkrytyk.pl/2021/12/15/video-swiete-wojny-javascriptu-ile-h1-na-stronie-i-dlaczego-kuba-przespal-ostatnie-10-lat/" rel="noreferrer noopener">nasza krytyka będzie całkowicie jałowa</a>.</p>
<p>Wreszcie: webdev najzwyczajniej w świecie <em>zasługuje</em> na bardziej naukowe – czy też po prostu merytoryczne – traktowanie. Choćby dlatego, że Sieć stanowi coraz większą część&nbsp;naszego codziennego życia. To wszechobecna technologia, do której <a href="https://www.itu.int/en/mediacentre/Pages/PR-2025-11-17-Facts-and-Figures.aspx" rel="noreferrer noopener">dostęp ma <strong>ok. 6 miliardów ludzi</strong></a>. W innych dziedzinach nauki i technologii, które często dotykają mniejszej liczby osób (jak np. projekt nowego skafandra astronautycznego), zachowujemy niezwykłą precyzję i naukową rygorystyczność. Natomiast w dziedzinie, która w taki czy inny sposób zahacza o <em>jakieś ¾ światowej populacji</em>, mam wrażenie, że często wychodzimy z założenia, że <q>to tylko technologia</q>. A to przecież nieprawda. <a href="https://shkspr.mobi/blog/2021/01/the-unreasonable-effectiveness-of-simple-html/" rel="noreferrer noopener">Ludzie mają różne powody, by korzystać z Internetu</a>. Jeśli – jako osoby tworzące strony WWW – nie traktujemy Sieci wystarczająco poważnie, ostatecznie szkodzimy osobom, które z tych stron WWW następnie korzystają. I myślę, że przestawienie myślenia z <q>to tylko technologia</q> w kierunku <q>webdev to nauka</q> jest mało bolesnym sposobem na stworzenie lepszej Sieci.</p>
<p>I tym optymistycznym akcentem życzę Wszystkim wesołych świąt 🎄🎄🎄!</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Tik-tak</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/tik-tak" rel="alternate" type="text/html"/>
			<published>2025-02-15T22:44:00.000Z</published>
			<updated>2025-02-15T22:44:00.000Z</updated>
			<id>https://blog.comandeer.pl/tik-tak</id>
			
				<summary><![CDATA[Jak ulepszyć czasomierze w przeglądarce?]]></summary>
			
			<content type="html"><![CDATA[<p>W przeglądarce dostępnych jest dużo różnych API. Niektóre z nich nieustannie ewoluują czy wręcz zostają zastąpione przez nowsze, lepiej zaprojektowane API. Inne z kolei wydają się całkiem zapomniane – jak choćby czasomierze (<i lang="en">timers</i>). Ostatnio używałem ich w małym projekcie i naszła mnie garść refleksji, którymi postanowiłem się podzielić&nbsp;ze światem. </p>
<h2 id="czym-są-czasomierze"><a class="header-anchor" href="https://blog.comandeer.pl/tik-tak#czym-są-czasomierze">Czym są czasomierze?</a></h2>
<p>Czasomierze (liczniki czasu, timery) to przeglądarkowe API, które służy do odliczania czasu. Jest <a href="https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers" rel="noreferrer noopener">dokładnie opisane w specyfikacji HTML</a> i składa się&nbsp;z czterech globalnych funkcji:</p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout" rel="noreferrer noopener"><code>setTimeout()</code></a> – służącej do jednorazowego odliczenia czasu i wykonania po tym czasie jakiejś&nbsp;czynności,</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/setInterval" rel="noreferrer noopener"><code>setInterval()</code></a> – służącej do wykonywania jakiejś czynności co określony czas,</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/clearTimeout" rel="noreferrer noopener"><code>clearTimeout()</code></a> – służącej do anulowania odliczania czasu rozpoczętego przy pomocy <code>setTimeout()</code>,</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/clearInterval" rel="noreferrer noopener"><code>clearInterval()</code></a> – służącej do anulowania odliczania czasu rozpoczętego przy pomocy <code>setInterval()</code>.</li>
</ul>
<p>Przy ustawianiu czasomierza należy podać czynność, którą chcemy wykonać, oraz czas, po jakim ma zostać wykonana (w przypadku <code>setTimeout()</code>), lub odstęp między poszczególnymi wykonaniami czynności (w przypadku <code>setInterval()</code>):</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">setTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( () </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'timeout'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">5000</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">setInterval</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( () </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'tik'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 4</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">1000</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>W przypadku <code>setTimeout()</code>  chcemy, żeby po 5 sekundach (1) wyświetlił się w konsoli napis <code>'timeout'</code> (2). W przypadku <code>setInterval()</code> z kolei chcemy, żeby co sekundę (3) wyświetlał się w konsoli napis <code>'tik'</code> (4). Warto przy tym zwrócić uwagę na dwie rzeczy. Pierwsza z nich to fakt, że czas należy podać w milisekundach. Druga to fakt, że <em>nie ma</em> gwarancji, że czasomierz dokładnie odmierzy podany czas. Przeglądarka zagwarantuje jedynie, że odmierzy <em>co najmniej</em> tyle, ile jej kazano –&nbsp;a więc <em>co najmniej</em> 5 sekund w przypadku <code>setTimeout()</code> oraz <em>co najmniej</em> sekundę w przypadku <code>setInterval()</code>. Zależy to od wielu czynników, <a href="http://m.in" rel="noreferrer noopener">m.in</a>. od tego, czy przeglądarka ma akurat “wolne”, czy procesor nie jest zajęty przez coś innego, czy okno przeglądarki jest sfocusowane, itd.</p>
<p>Zarówno <code>setTimeout()</code>, jak i <code>setInterval()</code> zwracają id, które może być następnie użyte w <code>clearTimeout()</code> i <code>clearInterval()</code>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> timeoutId</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> setTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( timeoutHandler, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">5000</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> );</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> intervalId</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> setInterval</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( intervalHandler, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">1000</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">clearTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( timeoutId );</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">clearInterval</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( intervalId );</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Żeby nie było za prosto, <code>clearTimeout()</code> zadziała też dla czasomierza stworzonego przez <code>setInterval()</code>, a <code>clearInterval()</code> – dla czasomierza stworzonego przez <code>setTimeout()</code>. Wynika to z tego, że <a href="https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#map-of-settimeout-and-setinterval-ids" rel="noreferrer noopener">standard przewiduje wspólną kolejkę dla obydwu typów czasomierzy</a>.</p>
<p>Choć czasomierze są API przeglądarkowym, są na tyle użyteczne, że pojawiły się także w pozostałych środowiskach uruchomieniowych JS-a: <a href="https://nodejs.org/docs/latest/api/timers.html" rel="noreferrer noopener">Node</a>, <a href="https://bun.sh/docs/runtime/web-apis" rel="noreferrer noopener">Bunie</a> i <a href="https://docs.deno.com/api/web/platform" rel="noreferrer noopener">Deno</a>. Niemniej implementacje czasomierzy między przeglądarkami a resztą środowisk mogą się nieco różnić. Dlatego w tym artykule skupiam się głównie na przeglądarkach.</p>
<h2 id="istniejące-problemy"><a class="header-anchor" href="https://blog.comandeer.pl/tik-tak#istniejące-problemy">Istniejące problemy</a></h2>
<p>Tak po prawdzie czasomierze nie zmieniły się od czasu swojego powstania. A to oznacza, że ominął je szereg rewolucji i ewolucji, jakie zaszły w JS-owym ekosystemie przez ostatnie, cóż, <em>dekady</em>. Osobiście widzę trzy główne problemy:</p>
<ul>
<li>przestarzałe podejście do asynchroniczności,</li>
<li>brak współpracy z <a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortController" rel="noreferrer noopener"><code>AbortController</code>em</a>,</li>
<li>nieprzyjazny format czasu.</li>
</ul>
<p>Przyjrzyjmy się pokrótce każdemu z tych problemów.</p>
<div class="note" role="note" aria-labelledby="note-63"><p class="note__label" id="note-63">Dygresja</p><div class="note__content"><p>W przykładach wykorzystuję <code>setTimeout()</code>, ale wszystkie opisane problemy występują także w przypadku <code>setInterval()</code>.</p></div></div>
<h3 id="przestarzałe-podejście-do-asynchroniczności"><a class="header-anchor" href="https://blog.comandeer.pl/tik-tak#przestarzałe-podejście-do-asynchroniczności">Przestarzałe podejście do asynchroniczności</a></h3>
<p>ES2015 przyniosło prawdziwą rewolucję związana ze zmianą podejścia do asynchroniczności w JS-ie. Polegała ona na odejściu od <a href="https://developer.mozilla.org/en-US/docs/Glossary/Callback_function" rel="noreferrer noopener">funkcji zwrotnych (<i lang="en">callbacks</i>)</a> na rzecz <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise" rel="noreferrer noopener">obietnic (<code>Promise</code>)</a>. Zmianę tą bardzo ładnie widać na przykładzie API w Node, np. do odczytywania zawartości pliku. Starsza, callbackowa wersja (<a href="https://nodejs.org/api/fs.html#fsreadfilepath-options-callback" rel="noreferrer noopener"><code>fs.readFile()</code></a>) doczekała się obietnicowego odpowiednika (<a href="https://nodejs.org/api/fs.html#fspromisesreadfilepath-options" rel="noreferrer noopener"><code>fsPromises.readFile()</code></a>):</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { readFile </span><span style="color:#D73A49;--shiki-dark:#F97583">as</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> readFileCallback } </span><span style="color:#D73A49;--shiki-dark:#F97583">from</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'node:fs'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { readFile </span><span style="color:#D73A49;--shiki-dark:#F97583">as</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> readFilePromise } </span><span style="color:#D73A49;--shiki-dark:#F97583">from</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'node:fs/promises'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">readFileCallback</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'/jakis-plik'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'utf-8'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, ( </span><span style="color:#E36209;--shiki-dark:#FFAB70">err</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">data</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( data ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 4</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">} );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> data</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#D73A49;--shiki-dark:#F97583"> await</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> readFilePromise</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'/jakis-plik'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'utf-8'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 5</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Starą wersję APi importujemy jako <code>readFileCallback()</code> (1), natomiast nową – <code>readFilePromise()</code> (2). Stara wersja przyjmuje trzy parametry (3): ścieżkę do pliku, opcje (w tym przypadku podajemy samo kodowanie pliku) oraz callback. W callbacku wyświetlamy odczytaną&nbsp;zawartość pliku (4). W przypadku obietnicowego API całość sprowadzona jest do jednej linijki (5). Sama funkcja przyjmuje też&nbsp;tylko dwa parametry – wszak callback nie jest potrzebny. Dzięki zastosowaniu tutaj <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#top_level_await" rel="noreferrer noopener">top-level <code>await</code></a> przypisujemy zawartość pliku do zmiennej <code>data</code> – dokładnie tak, jakbyśmy zrobili to w przypadku synchronicznego kodu.</p>
<p>Podobny proces zaszedł w przypadku sporej liczby technologii przeglądarkowych, np. oparty na obietnicach <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API" rel="noreferrer noopener"><code>fetch()</code></a> w wielu wypadkach wyparł stare, oparte na zdarzeniach API <a href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest" rel="noreferrer noopener"><code>XMLHttpRequest</code></a>. Obietnice, zwłaszcza w połączeniu z top-level <code>await</code>, pozwalają na pisanie kodu asynchronicznego w bardzo podobny sposób, jak kodu synchronicznego. Nie mamy już więcej do czynienia z tzw. <a href="http://callbackhell.com/" rel="noreferrer noopener">piekłem callbacków</a>, kod jest <em>płaski</em>.</p>
<p>Niemniej w przypadku czasomierzy jedyną dostępną opcją są właśnie callbacki, przez co jesteśmy zmuszani do zagnieżdżania kodu. Co więcej, w przypadku nowoczesnego asynchronicznego kodu, opartego na <code>Promise</code>, mieszanie go z callbackami jest najzwyczajniej w świecie niewygodne:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">await</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> someAsyncAction</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">setTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#D73A49;--shiki-dark:#F97583">async</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> () </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	await</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> someOtherAsyncAction</span><span style="color:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	await</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> yetAnotherAsyncAction</span><span style="color:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">1000</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Na początku wywołujemy jakąś asynchroniczną akcję (1). Kolejne dwie muszą być wywołane po jednej sekundzie odstępu (2). To sprawia, że muszą zostać przeniesione do wewnątrz wywołania <code>setTimeout()</code> (3). W momencie, gdy później pojawić musiałby się kolejny czasomierz, całość zrobiłaby się jeszcze bardziej zagnieżdżona.</p>
<p>Najczęściej taka sytuacja kończy się napisaniem jakiejś pomocniczej funkcji, która otaczałaby callbackowy kod w obietnicę:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">await</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> someAsyncAction</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">await</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> wait</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#005CC5;--shiki-dark:#79B8FF">1000</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">await</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> someOtherAsyncAction</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">await</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> yetAnotherAsyncAction</span><span style="color:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">async</span><span style="color:#D73A49;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> wait</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">time</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 4</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	return</span><span style="color:#D73A49;--shiki-dark:#F97583"> new</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> Promise</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( ( </span><span style="color:#E36209;--shiki-dark:#FFAB70">resolve</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 5</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">		setTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( resolve, time ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 6</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	} );</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Nasz kod wywołuje najpierw pierwszą asynchroniczną akcję (1), następnie naszą pomocniczą funkcję <code>wait()</code> (2), a następnie – resztę asynchronicznego kodu (3). Z kolei pomocnicza funkcja przyjmuje jako argument czas, który ma odliczyć (4), a następnie zwraca obietnicę (5). Wewnątrz obietnicy wywołujemy <code>setTimeout()</code> (6). Jako callback przekazujemy funkcję <code>resolve()</code> obietnicy oraz czas przekazany do samego <code>wait()</code>. Dzięki temu czasomierz rozwiąże obietnicę, gdy odliczanie czasu się zakończy.</p>
<h3 id="brak-współpracy-z-abortcontrollerem"><a class="header-anchor" href="https://blog.comandeer.pl/tik-tak#brak-współpracy-z-abortcontrollerem">Brak współpracy z <code>AbortController</code>em</a></h3>
<p><a href="https://blog.comandeer.pl/i-ciecie">Jak już kiedyś pisałem</a>, <code>AbortController</code> pozwala na eleganckie przerywanie operacji asynchronicznych “z zewnątrz”. Chyba sztandarowym przykładem jest przerywanie pobierania pliku po przekroczeniu określonego czasu oczekiwania:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#D73A49;--shiki-dark:#F97583">async</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> () </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> abortController</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#D73A49;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> AbortController</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	const</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#005CC5;--shiki-dark:#79B8FF">signal</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> } </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> abortController; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 4</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">	setTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( () </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 5</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		abortController.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">abort</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'timeout'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 7</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	}, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">2000</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 6</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	try</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> response</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#D73A49;--shiki-dark:#F97583"> await</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> fetch</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'https://slowfil.es/file?type=png&amp;delay=10000'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 8</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">			signal </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 9</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		} );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( response ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 10</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	} </span><span style="color:#D73A49;--shiki-dark:#F97583">catch</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( err ) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">error</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( err ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 11</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">} )();</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Całość otoczona jest w samowywołującą się funkcję asynchroniczną (1) – wszystko z powodu bloku <code>try</code>/<code>catch</code> (2). Wewnątrz funkcji tworzymy <code>AbortController</code> (3) oraz wyciągamy z niego <a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal" rel="noreferrer noopener">sygnał</a> (4). Następnie tworzymy czasomierz (5) i ustawiamy go na 2 sekundy (6). Jako callback wywołujemy metodę <code>abort()</code> naszego <code>AbortController</code>a (7). Jako powód przerwania operacji podajemy <code>'timeout'</code>. Następnie, w bloku <code>try</code> (2), tworzymy żądanie do pliku <code>.png</code> (8). Korzystam tutaj z <a href="https://slowfil.es/" rel="noreferrer noopener">usługi Slowfil.es</a>, która pozwala symulować powolną odpowiedź serwera poprzez ustawienie czasu odpowiedzi. W naszym wypadku obrazek zostanie zwrócony dopiero po 10 sekundach – a więc dawno po odliczeniu czasu przez czasomierz. Do <code>fetch()</code>a przekazujemy także sygnał jako <a href="https://developer.mozilla.org/en-US/docs/Web/API/RequestInit#signal" rel="noreferrer noopener">opcję <code>signal</code></a> (9). W przypadku, gdy żądanie się powiedzie, wyświetlamy odpowiedź z serwera (10). W innym wypadku – wyświetlamy błąd (11).</p>
<p class="note">Blok <code>try</code>/<code>catch</code> wymusił otoczenie całego kodu w asynchroniczną funkcję, ponieważ top-level <code>await</code> – jak sama nazwa wskazuje – działa tylko na <em>najwyższym</em> poziomie modułu. Jakiekolwiek zagłębienie jest już poniżej tego poziomu.
</p><p>Jeśli uruchomimy powyższy kod, zauważymy, że po około dwóch sekundach w konsoli pojawi się błąd <code>'timeout'</code>, a żądanie zostanie przerwane i odpowiedź z serwera nigdy nie trafi do konsoli.</p>
<p>Innym ciekawym przykładem zastosowania <code>AbortController</code>a jest anulowanie wielu operacji asynchronicznych naraz. Zmodyfikujmy nieco nasz przykład:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#D73A49;--shiki-dark:#F97583">async</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> () </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> abortController</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#D73A49;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> AbortController</span><span style="color:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	const</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#005CC5;--shiki-dark:#79B8FF">signal</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> } </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> abortController;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">	setTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( () </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		abortController.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">abort</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'timeout'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> );</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	}, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">2000</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	try</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> promise1</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> fetch</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'https://slowfil.es/file?type=png&amp;delay=10000'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">			signal </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 4</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		} );</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> promise2</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> fetch</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'https://slowfil.es/file?type=png&amp;delay=20000'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">			signal </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 5</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		} );</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> promise3</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> fetch</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'https://slowfil.es/file?type=png&amp;delay=30000'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">			signal </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 6</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		} );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		await</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> Promise</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">all</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( [ </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 7</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">			promise1,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">			promise2,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">			promise3</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		] );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">        console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'Done'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 8</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	} </span><span style="color:#D73A49;--shiki-dark:#F97583">catch</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( err ) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">error</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( err );</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">} )();</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Większość kodu pozostała bez zmian. Zamiast jednak jednego żądania, mamy w tym wypadku trzy (1, 2, 3). Są one tworzone na początku przez przypisanie obietnic zwróconych przez <code>fetch()</code> do zmiennych. Do każdego wywołania <code>fetch()</code> przekazujemy ten sam sygnał (4, 5, 6). Następnie czekamy na wykonanie się wszystkich żądań przy pomocy <code>Promise.all()</code> (7). gdy wszystkie żądania się&nbsp;zakończą, w konsoli wyświetlamy napis <code>'Done'</code> (8). Tak się jednak nie stanie, bo wszystkie żądania zostaną przerwane przez czasomierz.</p>
<p>Jeśli spojrzymy teraz do <a href="https://developer.chrome.com/docs/devtools/network" rel="noreferrer noopener">zakładki <i lang="en">Network</i> w narzędziach developerskich przeglądarki</a>, zauważymy, że, faktycznie, żadne z trzech żądań nie zostało dokończone:</p>
<figure class="figure"><a class="figure__link" href="/assets/images/tik-tak/devtools-1024w.avif"><picture><source type="image/avif" srcset="/assets/images/tik-tak/devtools-440w.avif 440w, /assets/images/tik-tak/devtools-880w.avif 880w, /assets/images/tik-tak/devtools-1024w.avif 1024w" sizes="90vw"><source type="image/webp" srcset="/assets/images/tik-tak/devtools-440w.webp 440w, /assets/images/tik-tak/devtools-880w.webp 880w, /assets/images/tik-tak/devtools-1024w.webp 1024w" sizes="90vw"><source type="image/png" srcset="/assets/images/tik-tak/devtools-440w.png 440w, /assets/images/tik-tak/devtools-880w.png 880w, /assets/images/tik-tak/devtools-1024w.png 1024w" sizes="90vw"><img src="/assets/images/tik-tak/devtools-1024w.png" width="1024" height="98" style="aspect-ratio: 1024 / 98" alt="Zakładka Network w narzędziach developerskich Chrome'a: wszystkie trzy żądania po obrazki w usłudze Slowfil.es są oznaczone jako anulowane." loading="lazy" decoding="async"></picture></a><figcaption class="figure__caption"><p>Kliknij obrazek, aby go powiększyć</p></figcaption></figure>
<p>Wyobraźmy sobie jednak inną sytuację: mamy kilka czasomierzy na stronie i w momencie, gdy w jednym z nich dochodzi do błędu, reszta również powinna być anulowana. Brak obsługi <code>AbortController</code>a wymusza tak naprawdę trzymanie id wszystkich czasomierzy i anulowanie ich pojedynczo:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> timeouts</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> [</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">	setTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( () </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">		stopTimeouts</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 5</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	}, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">1000</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ),</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">	setTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( () </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'#2'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> );</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	}, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">2000</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ),</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">	setTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( () </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 4</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'#3'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> );</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	}, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">3000</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> )</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">];</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> stopTimeouts</span><span style="color:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'stop'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	timeouts.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">forEach</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( ( </span><span style="color:#E36209;--shiki-dark:#FFAB70">timeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 6</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">		clearTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( timeout ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 7</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	} );</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Tworzymy tablicę <code>timeouts</code> (1), w której umieszczamy trzy wywołania <code>setTimeout()</code> (2, 3, 4). Dzięki temu tworzymy tablicę zawierającą id wszystkich czasomierzy. W pierwszym z nich symulujemy błąd, wywołując funkcję <code>stopTimeouts()</code> (5). Funkcja ta iteruje po tablicy <code>timeouts</code> (6) i dla każdego czasomierza wywołuje funkcję <code>clearTimeout()</code> (7).</p>
<p>I choć to rozwiązanie działa, jest zdecydowanie mniej eleganckie od obsługi przerywania asynchronicznych aplikacji przy pomocy <code>AbortController</code>a. Co więcej, robi się zdecydowanie bardziej kłopotliwe w momencie, gdy przerwanie ma nastąpić gdzieś z zewnątrz – z poziomu kodu, który niekoniecznie wie (czy wręcz: <em>powinien</em> wiedzieć), że gdzieś są wykorzystywane czasomierze. W idealnym świecie taki zewnętrzny kod mógłby przesłać sygnał do środka funkcji z asynchroniczną operacją i, w razie potrzeby, użyć go do wymuszenia przerwania tej operacji. W przypadku czasomierzy nie da się tego zrobić bez napisania własnoręcznie tłumaczenia sygnału na odpowiednie wywołania <code>clearTimeout()</code>.</p>
<h3 id="nieprzyjazny-format-czasu"><a class="header-anchor" href="https://blog.comandeer.pl/tik-tak#nieprzyjazny-format-czasu">Nieprzyjazny format czasu</a></h3>
<p>To zarzut zdecydowanie mniejszego kalibru niż dwa poprzednie. Otóż, niekoniecznie lubię się&nbsp;z milisekundami. A dokładniej: z przeliczaniem wszystkiego na milisekundy. W przypadku sekund jeszcze jest łatwo, ale jeśli potrzeba czegoś w minutach, to już się robi kłopotliwie. Dlatego fajnie byłoby móc przekazywać do czasomierzy czas w jakimś innym formacie, np. tekstowym:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">setTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( () </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {}, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'1m23s'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> );</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Prawda, że ładniej?</p>
<h2 id="co-dalej"><a class="header-anchor" href="https://blog.comandeer.pl/tik-tak#co-dalej">Co dalej?</a></h2>
<p>Skoro znamy już problemy i bolączki natywnych czasomierzy w przeglądarkach, czas pomyśleć, w jaki sposób można by je rozwiązać. Ale to już temat na kolejny wpis!</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Podsumowanie 2024</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/podsumowanie-2024" rel="alternate" type="text/html"/>
			<published>2024-12-30T12:55:00.000Z</published>
			<updated>2024-12-30T12:55:00.000Z</updated>
			<id>https://blog.comandeer.pl/podsumowanie-2024</id>
			
				<summary><![CDATA[Krótko o tym, co się wydarzyło w 2024 roku]]></summary>
			
			<content type="html"><![CDATA[<p>Skoro rok 2024 się kończy, pora mu się przyjrzeć.</p>
<p>Otóż, to był rok <a href="https://gwd.comandeer.pl/" rel="noreferrer noopener">Good Web Design</a>. Na początku roku rzuciłem na ten projekt wszystkie swoje siły i udało się go dowieźć. Co prawda nie jest idealnie, ale jest <em>akceptowalnie</em>. Niestety, fokus na GWD oznaczał także, że reszta projektów leżała odłogiem. Najbardziej ucierpiał <a href="https://www.webkrytyk.pl/" rel="noreferrer noopener">WebKrytyk</a>, na którym w tym roku prawie nic się nie działo. Na tym blogu było zdecydowanie lepiej, udało się nawet <a href="https://blog.comandeer.pl/blog-2.0">zrobić spore zmiany</a>. Ale że żyję w JS-owym światku, to skończyłem przepisywać bloga na <a href="https://www.11ty.dev/" rel="noreferrer noopener">Eleventy</a> 2 de facto w momencie premiery Eleventy 3, więc czeka mnie jeszcze sporo zabawy… Natomiast najprzyjemniej pisało mi się <a href="https://blog.comandeer.pl/kreda-czyli-reakcja-lancuchowa">artykuł o kredzie</a> – lubię takie eksperymenty.</p>
<p>I to w sumie tyle. Szczęśliwego Nowego Roku 🍾!</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Zwrot deklaratywny</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/zwrot-deklaratywny" rel="alternate" type="text/html"/>
			<published>2024-06-30T20:51:00.000Z</published>
			<updated>2024-06-30T20:51:00.000Z</updated>
			<id>https://blog.comandeer.pl/zwrot-deklaratywny</id>
			
			<content type="html"><![CDATA[<p>Webdevelopment przyzwyczaił nas już do tego, że pewnych rzeczy bez JS-a nie da się zrobić. Niektóre ficzery przeglądarkowe są bowiem dostępne tylko z poziomu JS-owych API. Inne rzeczy w ogóle nie są dostępne w przeglądarce i trzeba sobie je samemu napisać w – a jakże! – JS-ie. Niemniej ostatnio można zaobserwować pewien deklaratywny zwrot w standardach sieciowych.</p>
<h2 id="na-początku-był-popup"><a class="header-anchor" href="https://blog.comandeer.pl/zwrot-deklaratywny#na-początku-był-popup">Na początku był popup</a></h2>
<p>Chyba każda osoba zajmująca się webdevelopmentem przynajmniej raz zetknęła się z popupem. Te wyskakujące okienka przyjmować mogą wszelkie możliwe formy, jednak przez wiele lata jedynym sensownym sposobem na ich stworzenie był JS. I choć po drodze pojawił się nawet <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog" rel="noreferrer noopener">element HTML <code>&lt;dialog&gt;</code></a>, to wciąż potrzebowaliśmy JS-a do jego otwarcia.</p>
<p>Ale to ostatnio się zmieniło, za sprawą nowego <a href="https://developer.mozilla.org/en-US/docs/Web/API/Popover_API" rel="noreferrer noopener">Popover API</a>. Pozwala ono zrobić z dowolnego elementu wyskakujące okienko:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">HTML</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">button</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> popovertarget</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"popover"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Otwórz okienko&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">button</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">div</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> id</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"popover"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> popover</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Jestem okienkiem!&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">div</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Ba, nic nie stoi na przeszkodzie, by połączyć Popover API z elementem <code>&lt;dialog&gt;</code>:</p>
<div><embed- src="https://codepen.io/Comandeer/pen/GRaWPPy"><p class="embed-fallback"><a href="https://codepen.io/Comandeer/pen/GRaWPPy" rel="noreferrer noopener">Przejdź bezpośrednio do osadzonej treści na CodePenie.</a></p></embed-></div>
<p>Zarówno element <code>&lt;dialog&gt;</code>, jak i Popover API nie wydają się być strasznie przełomowe. Niemniej diabeł tkwi w szczegółach! Własnoręczne rzeźbienie wyskakujących okienek wiąże się ze sporą liczbą wyzwań, wśród których wymienić można choćby odpowiednie zarządzanie focusem. Rzut oka na <a href="https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/dialog/" rel="noreferrer noopener"><cite lang="en">ARIA Practices</cite></a> pokazuje, jak dużo trzeba zrobić, żeby wyskakujące okienko było dostępne. W przypadku <code>&lt;dialog&gt;</code>u i Popover API te wszystkie rzeczy robi (a przynajmniej: powinna robić) za nas przeglądarka. Przeszliśmy dzięki temu od mówienia przeglądarce, <em>jak</em> coś zrobić do mówienia, <em>co</em> chcemy zrobić, a przeglądarka załatwia już całą resztę.</p>
<h2 id="rewolucja-kryjąca-się-za-rogiem"><a class="header-anchor" href="https://blog.comandeer.pl/zwrot-deklaratywny#rewolucja-kryjąca-się-za-rogiem">Rewolucja kryjąca się za rogiem</a></h2>
<p>Sama możliwość tworzenia wyskakujących okienek przy pomocy dwóch linijek HTML-a jest czymś niezwykłym. Ale wciąż jest tutaj kilka rzeczy, które można by usprawnić. Jak choćby fakt, że na razie nie da się w taki sposób tworzyć modalnych dialogów (czyli takich, które uniemożliwiają korzystanie z reszty strony, aż nie zostaną zamknięte). Te wciąż wymagają JS-owego API. Z drugiej strony okienka tworzone przy pomocy Popover API dość trudno ustawić w odpowiednim miejscu na ekranie, przez co trudno zrobić z nich np. tooltipy lub menu otwierane przyciskiem.</p>
<p>Tutaj jednak na scenę wkracza kolejna nowość, na razie dostępna tylko w Chrome – <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_anchor_positioning/Using" rel="noreferrer noopener">CSS <i lang="en">anchor positioning</i> (kotwiczenie pozycjonowania)</a>. Pozwala ona “przypiąć” dany element do innego elementu. Dzięki temu można uzyskać choćby wspomniane wcześniej rozwijane przyciskiem menu:</p>
<div><embed- src="https://codepen.io/Comandeer/pen/qBGzJQm"><p class="embed-fallback"><a href="https://codepen.io/Comandeer/pen/qBGzJQm" rel="noreferrer noopener">Przejdź bezpośrednio do osadzonej treści na CodePenie.</a></p></embed-></div>
<p>Kotwiczenie pozycjonowania to, wbrew pozorom, nie jest łatwy do rozwiązania problem. Wystarczy spojrzeć, jak popularne są rozwiązania pokroju <a href="https://floating-ui.com/" rel="noreferrer noopener">Floating UI</a> (dawny Popper), które ma <a href="https://www.npmjs.com/package/@floating-ui/dom" rel="noreferrer noopener"><strong>ponad 11 milionów pobrań tygodniowo</strong></a>. A mówimy tutaj wyłącznie o wersji vanilla JS – jeśli zliczyć także integracje z popularnymi frameworkami oraz poprzednią wersję, Poppera, ta liczba spokojnie się <em>podwoi</em>. I nie jest to jedyna biblioteka od tego. Zapotrzebowanie jest więc bardzo duże i miło zobaczyć, że przeglądarki zaczynają to robić za nas. Co w teorii oznacza, że powinno to działać wydajniej i… czyściej. W końcu obliczanie pozycji w JS-ie od zawsze brzmiało na obejście ograniczeń CSS-a.</p>
<p>To wciąż jednak nie wszystkie nowości, które mogą się wkrótce pojawić. <a href="https://open-ui.org/" rel="noreferrer noopener">Grupa Open UI</a>, dzięki której dostaliśmy choćby omawiane już wyżej Popover API, pracuje <a href="http://m.in" rel="noreferrer noopener">m.in</a>. nad <a href="https://open-ui.org/components/invokers.explainer/" rel="noreferrer noopener"><i lang="en">invokers</i> (wywoływaczami)</a>. Co ciekawe, w jej propozycji pojawia się przykład, o którym też już wspominałem – wywołanie modalnego dialogu:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">HTML</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">button</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> command</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"showModal"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> commandfor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-dialog"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Otwórz dialog&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">button</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">dialog</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> id</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-dialog"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Jestem dialogiem!&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">dialog</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Wywoływacze pozwalać będą na deklaratywne przypinanie do przycisków komend, takich jak otwieranie dialogu, sterowanie odtwarzaniem multimediów, ale też – całkowicie niestandardowych, które osoba webdeveloperska będzie mogła sama dodać. Te ostatnie będą już jednak wymagać kodu JS do obsługi.</p>
<p>Równocześnie, w bardzo podobnym duchu, Jeremy Keith zaproponował <a href="https://github.com/adactio/share-button-type/blob/gh-pages/explainer.md" rel="noreferrer noopener">deklaratywną wersję Web Share API</a>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">HTML</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">button</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> type</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"share"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Share this&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">button</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>A to otworzyło drogę do szerszej dyskusji nad tym, <a href="https://adactio.com/journal/20259" rel="noreferrer noopener">czy aby nie potrzebujemy większej liczby typów przycisków</a>.</p>
<p>Deklaratywny zwrot widać także w podejściu przeglądarek. Google ostatnio zaproponowało <a href="https://developer.chrome.com/blog/permission-element-origin-trial" rel="noreferrer noopener">nowy element HTML, <code>&lt;permission&gt;</code></a>. Jego zadaniem ma być ustandaryzowanie pytania osoby odwiedzającej strony o możliwość użycia tzw. potężnych ficzerów, jak choćby dostęp do mikrofonu czy kamery.</p>
<p>Deklaratywne rozwiązania wielu webdeveloperskich wyzwań zaczynają pojawiać się w technologiach sieciowych coraz częściej. Tym samym coraz więcej rzeczy można wyrzucić z naszego kodu JS i zdać się w pełni na przeglądarkę. W teorii pozwoli to znacząco odchudzić ilość kodu, jaki przesyłamy do przeglądarek, a tym samym – podnieść wydajność stron WWW. Równocześnie obniża się złożoność samych aplikacji internetowych – ta jest przenoszona do samych przeglądarek. Niejako równolegle dochodzi też do zmiany paradygmatu. Coraz ważniejsze staje się <a href="https://buildexcellentwebsit.es/" rel="noreferrer noopener">doradzanie przeglądarce, zamiast rozkazywania jej</a>. Zwłaszcza, że przecież najnowsze deklaratywne rozwiązania nie są pierwszymi – obecne w CSS-ie <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout/Relationship_of_grid_layout_with_other_layout_methods" rel="noreferrer noopener">systemy layoutu (flexbox i grid)</a> też są w pełni deklaratywne, podobnie jak <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_logical_properties_and_values" rel="noreferrer noopener">właściwości logiczne</a>. Jako osoby webdeveloperskie musimy pamiętać o coraz mniejszej liczbie rzeczy, co sprawia, że możemy skupić się na tym, co najbardziej istotne: dowożeniu dobrych stron WWW.</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Zgniłe jabłko</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/zgnile-jablko" rel="alternate" type="text/html"/>
			<published>2024-02-28T17:17:00.000Z</published>
			<updated>2024-02-28T17:17:00.000Z</updated>
			<id>https://blog.comandeer.pl/zgnile-jablko</id>
			
				<summary><![CDATA[Apple vs PWA i UE.]]></summary>
			
			<content type="html"><![CDATA[<p>Miesiąc temu Apple zostało zmuszone przez prawo unijne do <a href="https://www.apple.com/pl/newsroom/2024/01/apple-announces-changes-to-ios-safari-and-the-app-store-in-the-european-union/" rel="noreferrer noopener">otworzenia iOS-a na inne silniki przeglądarek</a>. To mógł być początek jednej z największych rewolucji mobilnych i kolejny krok w stronę naprawdę otwartego Internetu.</p>
<p>Ale tak się nie stało. Apple stwierdziło, że skoro na iOS-ie mają być dostępne inne przeglądarki, to <a href="https://open-web-advocacy.org/blog/its-official-apple-kills-web-apps-in-the-eu/" rel="noreferrer noopener">należy wszystkim przeglądarkom odebrać jak najwięcej możliwości</a>. Tym samym osoby używające iOS-a w Unii zostaną pozbawione sensownej możliwości używania PWA. Co to dokładnie oznacza, opisali dobrze <a href="https://blog.tomayac.com/2024/02/28/so-what-exactly-did-apple-break-in-the-eu/" rel="noreferrer noopener">Thomas Steiner</a> oraz <a href="https://infrequently.org/2024/02/home-screen-advantage/" rel="noreferrer noopener">Alex Russell</a>. Jeśli Apple uda się bezkarnie przeprowadzić tę akcję, rozwój Sieci mobilnej na urządzeniach z jabłkiem cofnie się o <em>dosłownie lata</em>.</p>
<p>Jeśli, podobnie jak ja, nie zgadzasz się z działaniami Apple’a, zachęcam do podpisania <a href="https://letter.open-web-advocacy.org/" rel="noreferrer noopener">listu otwartego do Tima Cooka</a>.</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Uparte narzędzia</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/uparte-narzedzia" rel="alternate" type="text/html"/>
			<published>2024-01-31T19:59:00.000Z</published>
			<updated>2024-01-31T19:59:00.000Z</updated>
			<id>https://blog.comandeer.pl/uparte-narzedzia</id>
			
				<summary><![CDATA[Czy nadmiar konfigurowalności jest dobry?]]></summary>
			
			<content type="html"><![CDATA[<p>Nie tak dawno temu na fejsowych grupkach poświęconych webdevowi zobaczyłem dwie rzeczy. Jedną z nich był news o nowej wersji jakiejś popularnej JS-owej biblioteki, w którym autor zastanawiał się, jak przetłumaczyć na polski słówko <i lang="en">opinionated</i>. Drugą był kod startera dla aplikacji w Next.js i Tailwindzie, który zawierał 20 plików konfiguracyjnych (22, jeśli doliczyć licencję i plik <code>README</code>). Co te posty mają ze sobą wspólnego? W sumie to nic, ale skłoniły mnie do dzisiejszej refleksji.</p>
<p>20 plików konfiguracyjnych. Swego czasu całe moje projekty posiadały mniej plików, niż ten starter konfiguracji. Ale potem coś się zdecydowanie zmieniło. Gdy teraz spoglądam na kod <a href="https://github.com/Comandeer/rollup-lib-bundler" rel="noreferrer noopener">swojego bundlera</a>, z przerażeniem stwierdzam, że metaplików (a więc zawierających jakąś konfigurację lub inne informacje o projekcie, nie zaś kod) jest podejrzanie wręcz dużo (15 plików i dwa katalogi). Na dobrą sprawę jest ich więcej niż faktycznego kodu. I z jednej strony to fantastycznie, że w JS-owym ekosystemie możemy sobie skonfigurować absolutnie wszystko, łącznie z dokładnym sposobem formatowania kodu. Tylko pytanie, czy na pewno potrzebujemy aż takiej swobody?</p>
<p>W PHP swego czasu rozwiązano ten problem dość efektywnie. Powstała <s>grupa trzymająca władzę</s> nieoficjalna organizacja tworząca standardy, <a href="https://www.php-fig.org/" rel="noreferrer noopener">PHP Framework Interop Group (PHP-FIG)</a>. Jej rekomendacje są przestrzegane przez większość PHP-owego światka, dzięki czemu ustalono <a href="http://m.in" rel="noreferrer noopener">m.in</a>. <a href="https://www.php-fig.org/psr/psr-4/" rel="noreferrer noopener">standard nazewnictwa i automatycznego wczytywania modułów</a> czy <a href="https://www.php-fig.org/psr/psr-12/" rel="noreferrer noopener">sposób formatowania kodu</a>. Jasne, te rekomendacje nie mają żadnej mocy prawnej i w teorii nic się nie stanie, jeśli kod w PHP nie będzie ich przestrzegał. Ale stały się de facto standardem w PHP-owym ekosystemie i większość <em>profesjonalnego</em> kodu (zwłaszcza frameworków) będzie przestrzegała określonych przez PHP-FIG reguł. I dzięki temu, mimo że sam język nie definiuje dokładnych reguł dla wczytywania klas na podstawie ich nazwy, taka oddolna inicjatywa standaryzacyjna pozwoliła na stworzenie managera zależności, <a href="https://getcomposer.org/" rel="noreferrer noopener">Composera</a>. Tymczasem w ekosystemie JS-a teoretycznie mamy dość dobrze opisany sposób wczytywania modułów na poziomie samego języka. A mimo to ostatecznie niemal każdy framework ma swoje własne zdanie na temat tego, jak to powinno ostatecznie wyglądać. Nie wspominając już o tym, że manager pakietów też trzeba sobie najpierw wybrać.</p>
<p>I tutaj na scenę wkraczają <i lang="en">opinionated</i> rozwiązania. To angielskie słówko tłumaczy się dosłownie jako “uparty” i… podoba mi się to tłumaczenie. Bo tak, myślę, że potrzebujemy więcej upartych rozwiązań. Takich, które pozwalają zrobić coś w jeden, ściśle określony sposób. I których się nie da konfigurować. Dobrym przykładem może być formator kodu. Zwykle w projekcie używa się jednego sposobu zapisu. Co oznacza, że formator nie musi mieć miliarda opcji konfiguracyjnych, żeby dostosować każdy jeden przecinek i średnik. Wystarczy, żeby robił to tak, jak <em>powinno być</em>. Albo bundler. Konfiguracja webpacka jest już dzisiaj memem w branży, bo praktycznie nikt nie chce (czy wręcz nie jest w stanie) zrobić tego ręcznie. A przecież naprawdę sporo rzeczy można wyciągnąć bezpośrednio z pliku <code>package.json</code>, np. nazwy bundle’i, wersje Node’a, jakie mają być wspierane, itd. <a href="https://github.com/Comandeer/rollup-lib-bundler#how-does-it-work" rel="noreferrer noopener">Mój bundler właśnie coś takiego robi</a>, dzięki czemu nie potrzebuje żadnej dodatkowej konfiguracji. I chciałbym widzieć takich narzędzi zdecydowanie więcej.</p>
<p>Tak, cenię sobie konfigurowalność JS-owych narzędzi. Ale czasami po prostu chcę je zainstalować i nie musieć się ani razu zastanowić nad tym, czy aby na pewno są poprawnie skonfigurowane.</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Podsumowanie 2023</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/podsumowanie-2023" rel="alternate" type="text/html"/>
			<published>2023-12-31T17:38:00.000Z</published>
			<updated>2023-12-31T17:38:00.000Z</updated>
			<id>https://blog.comandeer.pl/podsumowanie-2023</id>
			
				<summary><![CDATA[Lista sukcesów w 2023 roku.]]></summary>
			
			<content type="html"><![CDATA[<p>Kolejny rok się kończy, tym razem 2023. Zatem pora na podsumowania.</p>
<p>To był dość powolny rok, nie działo się za dużo, ani w projektach, ani na blogach. <a href="https://www.webkrytyk.pl/" rel="noreferrer noopener">WebKrytyk</a> mocno zwolnił tempo, ale być może w przyszłym trochę je odzyska. Aczkolwiek chyba faktycznie będzie powoli zmierzać w kierunku bardziej edukacyjnego podejścia. Z kolei na tym blogu najbardziej dumny jestem z <a href="https://blog.comandeer.pl/makrony.html">tekstu o makrach</a>. Natomiast nie jestem dumny z tego, że w tym roku niemal całkowicie odpuściłem tematy związane z dostępnością czy standardami sieciowymi. W przyszłym roku powinno być lepiej pod tym względem.</p>
<p>I tym optymistycznym akcentem kończymy 2023 i zaczynamy 2024!</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Web 3.0</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/web-3.0" rel="alternate" type="text/html"/>
			<published>2023-12-29T18:11:00.000Z</published>
			<updated>2023-12-29T18:11:00.000Z</updated>
			<id>https://blog.comandeer.pl/web-3.0</id>
			
				<summary><![CDATA[Nowa Sieć jako Sieć dla ludzi i maszyn.]]></summary>
			
			<content type="html"><![CDATA[<p>Z Web 3.0 jest jak z końcem świata – przeżyłem ich już kilka. Obecna wersja (zapisywana jako “Web3”) ma odmienić oblicze Sieci przy pomocy <a href="https://adactio.com/articles/20290" rel="noreferrer noopener">mieszanki blockchaina i AI</a>, ale <a href="https://web3isgoinggreat.com/" rel="noreferrer noopener">średnio to na razie wychodzi</a>. Dlatego nie o tym Web 3.0 będzie dzisiaj.</p>
<p>Paradoksalnie najtrwalszym pomysłem na nową wersję Sieci była pierwotna wizja, która powstała lata temu i w którą swego czasu bardzo mocno zaangażowało się W3C. Pomysł był żywy już w 2007 roku, gdy <a href="https://www.w3.org/2007/Talks/0123-sb-W3CEmergingTech/Overviewp.pdf" rel="noreferrer noopener">Steve Bratt prezentował go na forum tego ciała standaryzacyjnego</a>. W tej prezentacji pada także definicja tego, jak wyobrażano sobie wówczas nową Sieć (slajd 3):</p>
<blockquote>
<p><span lang="en" dir="ltr">Web of Data and Services</span></p>
<p>[Sieć danych i usług]</p>
</blockquote>
<p>Bardzo podoba mi się spójność całej wizji roztaczanej przez Bratta. Pokazuje on, w jaki sposób dzięki otwartym standardom sieciowym udało się stworzyć podwaliny Sieci dostępnej dla wszystkich i jak ekspansja na różne urządzenia, w tym mobilne, będzie kolejnym krokiem w ewolucji Sieci. Dzisiaj, <em>16 lat później</em>, wiemy już, że tak faktycznie było. Niemniej w ówczesnej wizji W3C po Sieci dostępnej dla wszystkich i na każdym urządzeniu miał nastąpić ostatni etap – Web 3.0, Sieć danych i usług. Miała to być Sieć, która byłaby zrozumiała zarówno dla ludzi, jak i dla maszyn. A to miało pozwalać na oferowanie wielu ciekawych usług, typu automatyczne wyszukiwanie ceny biletów i zamawianie ich, wyciąganie ze strony pobliskich restauracji menu, itd. Web 3.0 to miała być Sieć współpracująca z użytkownikami w celu jak najszybszego spełnienia ich celów.</p>
<p>Urzeczywistnić tę&nbsp;wizję miały nowe standardy sieciowe, wprowadzające technologie do opisywania danych w sposób umożliwiający ich maszynowe przetwarzanie. I takie technologie <a href="https://www.smashingmagazine.com/2020/10/developing-semantic-web/" rel="noreferrer noopener">faktycznie powstały</a>! Wśród nich warto wymienić trzy:</p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Glossary/RDF" rel="noreferrer noopener">RDF</a>/<a href="https://en.wikipedia.org/wiki/RDFa" rel="noreferrer noopener">RDFa</a>,</li>
<li><a href="https://json-ld.org/" rel="noreferrer noopener">JSON-LD</a>,</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Microdata" rel="noreferrer noopener">mikrodata</a>.</li>
</ul>
<p>Dzięki nim można dokładnie opisywać poszczególne fragmenty treści, np. informując, że fragment “25 złotych” to cena produktu. Takie informacje są następnie wykorzystywane <a href="http://m.in" rel="noreferrer noopener">m.in</a>. <a href="https://developers.google.com/search/blog/2019/04/enriching-search-results-structured-data" rel="noreferrer noopener">przez wyszukiwarki internetowe do tego, by prezentować dokładniejsze wyniki wyszukiwania</a>, czy <a href="https://twitter.com/rmondello/status/1109850097183911937" rel="noreferrer noopener">przez przeglądarki do lepszego prezentowania treści</a>. Dodatkowo wyszukiwarki internetowe (w tym Google i Bing) zgodziły się na <a href="https://schema.org/" rel="noreferrer noopener">wspólny format opisywania treści, Schema.org</a>.</p>
<p>Można by więc rzec, że wizja W3C urzeczywistniła się. I tak faktycznie się stało… ale do pewnego stopnia. Już w 2001 roku pojawiały się głosy, że <a href="https://people.well.com/user/doctorow/metacrap.htm" rel="noreferrer noopener">wizja w pełni semantycznej Sieci jest utopijna</a>. Dzisiaj można przyznać im rację. Web 3.0 zostało wdrożone tylko w małym kawałku, głównie w wyszukiwarkach. Dodatkowo stosunkowo mało autorów stron używa technologii do opisu treści, przez co nie są one tak użyteczne, jak mogłyby być. Nie ma też jednego formatu do opisu danych. Owszem, jest <a href="http://Schema.org" rel="noreferrer noopener">Schema.org</a>, ale znów – to inicjatywa głównie wyszukiwarek. Jeśli mówimy o innych narzędziach, to często zmuszone są one do ręcznego pobierania i parsowania treści różnych stron internetowych. Dodajmy do tego zamknięte silosy danych (takie jak wielkie sieci społecznościowe) i nagle okazuje się, że z Web 3.0 został smutny cień.</p>
<p>Chociaż być może prawdziwą przyszłością semantycznej Sieci zrozumiałej zarówno dla ludzi, jak i maszyn, wcale nie będą technologie do dodatkowego oznaczania treści. Jesteśmy w samym środku wielkiej rewolucji związanej ze sztuczną inteligencją, a dokładniej – wielkimi modelami językowymi (LLM). Ta technologia jest <a href="https://shkspr.mobi/blog/2023/05/does-ai-mean-we-dont-need-the-semantic-web/" rel="noreferrer noopener">dobra w rozumieniu języka naturalnego</a>. Być może zatem rozwój LLM sprawi, że praktycznie każda strona stanie się częścią Web 3.0. Zapewne byłby to ciekawy obrót sytuacji.</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Dostępność w nieoczekiwanych miejscach</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/dostepnosc-w-nieoczekiwanych-miejscach" rel="alternate" type="text/html"/>
			<published>2023-05-17T22:00:00.000Z</published>
			<updated>2023-05-17T22:00:00.000Z</updated>
			<id>https://blog.comandeer.pl/dostepnosc-w-nieoczekiwanych-miejscach</id>
			
				<summary><![CDATA[Dostępność można znaleźć w miejscach, w których byśmy się jej nie spodziewali.]]></summary>
			
			<content type="html"><![CDATA[<p>Dzisiaj, 18 maja 2023, obchodzimy kolejny <a href="https://accessibility.day/" rel="noreferrer noopener">Światowy Dzień Świadomości Dostępności (ang. <i lang="en">Global Accessibility Awareness Day</i>)</a>. Z tej okazji naszła mnie krótka refleksja o tym, jak postrzegana jest dostępność.</p>
<p>Gdyby zapytać przypadkową osobę webdeveloperską o to, czym jest dostępność, to jest duża szansa uzyskania odpowiedzi, że to dostosowanie strony tak, aby mogły z niej korzystać&nbsp;także osoby z niepełnosprawnościami. To prawda. Prawdopodobnie można by też usłyszeć o <a href="https://wcag21.lepszyweb.pl/" rel="noreferrer noopener">standardzie WCAG</a>. I to też jak najbardziej prawda. Problem polega na tym, że nie <em>cała</em> prawda.</p>
<p>Bo dostępność bardzo trudno zamknąć w obiektywnie weryfikowalny standard. Jasne, strona zgodna z WCAG ma zdecydowanie większe szanse być faktycznie dostępna, ale nawet <a href="https://wcag21.lepszyweb.pl/#background-on-wcag-2" rel="noreferrer noopener">wprowadzenie do tego standardu</a> zaznacza, że</p>
<blockquote>
<p>Chociaż wytyczne poruszają szereg zagadnień, nie jest możliwe, aby odpowiadały szczegółowo na potrzeby wszystkich możliwych rodzajów, stopni niepełnosprawności, czy też niepełnosprawności złożonych.</p>
</blockquote>
<p>WCAG to przede wszystkim zbiór dobrych, sprawdzonych w boju praktyk, których przestrzeganie pozwoli stworzyć podstawy dostępnego rozwiązania. Ale bardzo często, by stworzyć faktycznie dostępne rozwiązanie, trzeba pochylić się także nad innymi kwestiami – często takimi, które wcale nie są oczywiste.</p>
<p>Cofnijmy się nieco i spójrzmy, jak w ogóle definiowana jest dostępność. Bardzo ładną definicję znaleźć można na <a href="https://en.wikipedia.org/wiki/Web_accessibility" rel="noreferrer noopener">angielskiej Wiki</a>:</p>
<blockquote>
<p><strong>Web accessibility</strong>, or <strong>eAccessibility</strong>,[<a href="https://en.wikipedia.org/wiki/Web_accessibility#cite_note-sec1095-1" rel="noreferrer noopener">1]</a> is the <a href="https://en.wikipedia.org/wiki/Inclusion_(disability_rights)" rel="noreferrer noopener">inclusive practice</a> of ensuring there are no barriers that prevent interaction with, or access to, <a href="https://en.wikipedia.org/wiki/Website" rel="noreferrer noopener">websites</a> on the <a href="https://en.wikipedia.org/wiki/World_Wide_Web" rel="noreferrer noopener">World Wide Web</a> by people with physical <a href="https://en.wikipedia.org/wiki/Disabilities" rel="noreferrer noopener">disabilities</a>, situational disabilities, and socio-economic restrictions on bandwidth and speed.</p>
<p>[Dostępność stron WWW lub eDostępność to inkluzywna praktyka zapewniająca brak barier w dostępie i interakcji ze stronami WWW przez osoby z niepełnosprawnościami trwałymi i sytuacyjnymi oraz osoby z ograniczeniami socjo-ekonomicznymi dotyczącymi transferu i prędkości łącza.]</p>
</blockquote>
<p>W tej definicji pojawia się aspekt, którego zwykle nie łączy się z dostępnością: ograniczenia transferu i prędkości łącza. Innymi słowy – za dostępność uznać można także kwestie związane z wydajnością&nbsp;stron internetowych. Zwłaszcza, że <a href="https://www.smashingmagazine.com/2017/03/world-wide-web-not-wealthy-western-web-part-1/" rel="noreferrer noopener">Sieć to nie tylko szerokopasmowe łącza</a>. W chwili, gdy <a href="https://whatdoesmysitecost.com/" rel="noreferrer noopener">każdy bajt słono kosztuje</a>, rozmiar strony zaczyna ograniczać dostęp do niej w bardzo podobny sposób co niedostosowanie do osób z niepełnosprawnościami. I to wyłącznie od autora strony zależeć będzie, czy zapewni dostępność&nbsp;takim osobom, np. poprzez ograniczenie ilości wczytywanego JavaScriptu czy skorzystanie z dobrych praktyk (jak np. <a href="https://web.dev/vitals/" rel="noreferrer noopener">Web Vitals</a>).</p>
<p>Warto przy tym zauważyć, że WCAG nigdzie nie wspomina o wydajności. Stąd też ograniczenie się wyłącznie do spełniania wymogów standardu niekoniecznie uchroni nas przed odcięciem dostępu sporej liczbie użytkowników. I właśnie dlatego warto regularnie sprzątać&nbsp;pod metaforycznym łóżkiem naszej strony – bo możemy znaleźć&nbsp;problemy z dostępnością w naprawdę nieoczekiwanych miejscach.</p>
]]></content>
		</entry>
	
</feed>
