<?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/projekty/czasomierze.xml" rel="self" type="application/atom+xml"/>
	<link href="https://blog.comandeer.pl/projekty/czasomierze" rel="alternate" type="text/html"/>
	<updated>2026-09-07T18:40:22.477Z</updated>
	<id>https://blog.comandeer.pl/feeds/projekty/czasomierze.xml</id>
	<title type="html">Comandeerowy blog</title>
	
		<category term="Czasomierze"/>
		<subtitle>Projekt Czasomierze</subtitle>
	
	
		
		
	
	
		<entry>
			<title type="html">Pakując manatki</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/pakujac-manatki" rel="alternate" type="text/html"/>
			<published>2026-08-03T16:20:00.000Z</published>
			<updated>2026-08-03T16:20:00.000Z</updated>
			<id>https://blog.comandeer.pl/pakujac-manatki</id>
			
				<summary><![CDATA[Zaczynamy opakowywać czasomierze w pakiet npm!]]></summary>
			
			<content type="html"><![CDATA[<p><a href="https://blog.comandeer.pl/podsumowanie-2025">Tak jak obiecałem</a>, na początku tego roku – w ten piękny zimowy dzionek – zajmujemy się zrobieniem z naszych czasomierzy pełnoprawnego pakietu npm!</p>

<h2 id="erm"><a class="header-anchor" href="https://blog.comandeer.pl/pakujac-manatki#erm">Erm…</a></h2>
<p>Dzięki temu, że mamy styczeń, mamy też mnóstwo czasu na… Że co? Że jest już&nbsp;sierpień?!</p>
<figure class="figure"><a class="figure__link" href="/assets/images/pakujac-manatki/blink-meme-640w.avif"><picture><source type="image/avif" srcset="/assets/images/pakujac-manatki/blink-meme-440w.avif 440w, /assets/images/pakujac-manatki/blink-meme-640w.avif 640w" sizes="90vw"><source type="image/webp" srcset="/assets/images/pakujac-manatki/blink-meme-440w.webp 440w, /assets/images/pakujac-manatki/blink-meme-640w.webp 640w" sizes="90vw"><source type="image/jpeg" srcset="/assets/images/pakujac-manatki/blink-meme-440w.jpeg 440w, /assets/images/pakujac-manatki/blink-meme-640w.jpeg 640w" sizes="90vw"><img src="/assets/images/pakujac-manatki/blink-meme-640w.jpeg" width="640" height="394" style="aspect-ratio: 640 / 394" alt="Mężczyzna w styczniu zamyka oczy, by mrugnąć, ale gdy je otwiera, jest już sierpień." loading="lazy" decoding="async"></picture></a><figcaption class="figure__caption"><p>Ja w tym roku</p></figcaption></figure>
<p>No więc tak, projekt czasomierzy zdecydowanie zaliczył poślizg – ale hej! To raptem tylko 7 miesięcy, <a href="https://www.youtube.com/watch?v=NuAKnbIr6TE" rel="noreferrer noopener">nie ma powodów do paniki</a>. Wróćmy może do kodu…!</p>
<h2 id="tworzymy-repozytorium"><a class="header-anchor" href="https://blog.comandeer.pl/pakujac-manatki#tworzymy-repozytorium">Tworzymy repozytorium</a></h2>
<div class="note" role="note" aria-labelledby="note-73"><p class="note__label" id="note-73">Dygresja</p><div class="note__content"><p>Ten artykuł zakłada, że masz zainstalowanego <a href="https://git-scm.com/install/" rel="noreferrer noopener">gita</a> oraz <a href="https://nodejs.org/en/download" rel="noreferrer noopener">Node’a</a> z <a href="https://docs.npmjs.com/cli/v12/configuring-npm/install" rel="noreferrer noopener">npm-em</a>.</p></div></div>
<p>Jeśli spojrzymy na stronę przykładowego pakietu npm, np. <a href="https://www.npmjs.com/package/react" rel="noreferrer noopener">Reacta</a>, zauważymy, że składa się ona&nbsp;z dwóch części: dokumentacji pakietu oraz dodatkowych informacji o nim (instrukcji instalacji, liczby pobrań, itd.). Większość pakietów wśród tych informacji zawiera także linka do repozytorium, w którym znaleźć można ich kod źródłowy – tak jest też <a href="https://github.com/react/react" rel="noreferrer noopener">w przypadku Reacta</a>. Najczęściej takie repozytorium jest hostowane na <a href="https://github.com/" rel="noreferrer noopener">GitHubie</a> (GH). Na dzień dzisiejszy to największe miejsce, w którym rozwijane jest oprogramowanie open source. GitHub, jak sama nazwa wskazuje, został zbudowany wokół <a href="https://git-scm.com/" rel="noreferrer noopener">systemu kontroli wersji git</a>.</p>
<p>Z racji tego, że większość pakietów npm zaczyna się od repozytorium na GH i jest to w sumie dość&nbsp;wygodny sposób rozwijania oprogramowania open source, to nasze czasomierze również tam wylądują. W tym celu należy <a href="https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-new-repository" rel="noreferrer noopener">stworzyć repozytorium</a>. W trakcie tworzenia GitHub zapyta nas o parę rzeczy:</p>
<ul>
<li><strong>Nazwa repozytorium</strong> – z racji tego, że projekt nazywa się <cite>czasomierze</cite>, tak też wypada nazwać nasze repozytorium. Aczkolwiek nazwa może być całkowicie dowolna.</li>
<li><strong>Opis</strong> – krótki opis zawartości repozytorium. Można go całkowicie pominąć.</li>
<li><strong>Widoczność repozytorium</strong> – publiczne repozytoria są widoczne dla wszystkich, podczas gdy prywatne tylko dla nas i osób, które zaprosimy. W przypadku czasomierzy nie ma to większego znaczenia. Ja zwykle wybieram publiczne repozytoria, bo nie widzę potrzeby chowania kodu open source.</li>
<li><strong>Szablon</strong> – pewien czas temu GitHub dodał <a href="https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-template-repository" rel="noreferrer noopener">szablony</a>, czyli specjalny typ repozytoriów, które można wykorzystywać&nbsp;jako punkt startowy do własnych projektów. W przypadku czasomierzy szablon nie jest nam potrzebny.</li>
<li><strong>Plik <code>README</code></strong> – GitHub może stworzyć ten plik za nas.</li>
<li><strong>Plik <code>.gitignore</code></strong> – GitHub może stworzyć za nas również ten plik. Z listy wybrać można wykorzystywaną technologię, żeby był jak najlepiej dostosowany do projektu. W naszym przypadku “Node” może się dobrze sprawdzić.</li>
<li><strong>Plik <code>LICENSE</code></strong> – kolejny plik, który GitHub może wygenerować dla nas. Ja zwykle używam <a href="https://opensource.org/license/mit" rel="noreferrer noopener">licencji MIT</a>.</li>
</ul>
<div class="note" role="note" aria-labelledby="note-74"><p class="note__label" id="note-74">Dygresja</p><div class="note__content"><p>W dalszej części artykułu opisuję pokrótce te pliki.</p></div></div>
<p>Gdy już stworzymy repozytorium na GitHubie, wypada je <a href="https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository" rel="noreferrer noopener">sklonować lokalnie</a>, by móc na nim pracować:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">Shell</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">git</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> clone</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> git@github.com:Comandeer/czasomierze.git</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><h2 id="tworzymy-szkielet"><a class="header-anchor" href="https://blog.comandeer.pl/pakujac-manatki#tworzymy-szkielet">Tworzymy szkielet</a></h2>
<p>Gdy już mamy repozytorium postawione, trzeba w nim dodać pewne podstawowe pliki.</p>
<h3 id="gitignore"><a class="header-anchor" href="https://blog.comandeer.pl/pakujac-manatki#gitignore"><code>.gitignore</code></a></h3>
<p>Zacznijmy od magicznego <a href="https://git-scm.com/docs/gitignore" rel="noreferrer noopener">pliku <code>.gitignore</code></a>. Zawiera on listę plików i folderów, które git ma ignorować. Takie pliki nie znajdą się&nbsp;w repozytorium. Zwyczajowo omija się w ten sposób np. katalog <code>node_modules</code>, zawierający zależności, oraz katalog zawierający zbudowaną/skompilowaną wersję projektu. Stąd na potrzeby naszego projektu wypada dodać następujące wpisy do pliku:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage"></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>dist/</span></span>
<span class="line"><span>node_modules/</span></span>
<span class="line"><span></span></span></code></pre>

			</div>
		</figure><div class="note" role="note" aria-labelledby="note-75"><p class="note__label" id="note-75">Dygresja</p><div class="note__content"><p>Jeśli plik został wygenerowany przez GitHuba, prawdopodobnie zawiera już&nbsp;wszystkie potrzebne wpisy.</p><p>Natomiast kwestię pomijania akurat zależności i zbudowanej wersji pozwolę sobie poruszyć w osobnym artykule.</p></div></div>
<h3 id="packagejson"><a class="header-anchor" href="https://blog.comandeer.pl/pakujac-manatki#packagejson"><code>package.json</code></a></h3>
<p><a href="https://docs.npmjs.com/cli/v12/configuring-npm/package-json/" rel="noreferrer noopener">Plik <code>package.json</code></a> to podstawowy plik w każdym pakiecie npm. Zawiera wszystkie najważniejsze informacje o nim, takie jak nazwa, lista zależności, skrypty itd. Nie musimy go jednak tworzyć&nbsp;ręcznie, możemy wykorzystać do tego <a href="https://docs.npmjs.com/cli/v12/commands/npm-install" rel="noreferrer noopener">komendę <code>npm init</code></a>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">Shell</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">npm</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> init</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --y</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Komenda pyta o różne rzeczy, takie jak nazwa pakietu. Możemy to pominąc, dodając flagę <code>--y</code>. Informuje ona npm, że akceptujemy wszystkie sugerowane wartości. Dzięki temu powinniśmy dostać mniej więcej taki plik <code>package.json</code>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JSON</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>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "name"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"czasomierze"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "version"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"1.0.0"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "description"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"A simpler and friendler timers."</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "main"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"index.js"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "scripts"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    "test"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"echo </span><span style="color:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="color:#032F62;--shiki-dark:#9ECBFF">Error: no test specified</span><span style="color:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> &amp;&amp; exit 1"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  },</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "repository"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    "type"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"git"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    "url"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"git+https://github.com/Comandeer/czasomierze.git"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  },</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "keywords"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: [],</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "author"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">""</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "license"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"ISC"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "type"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"commonjs"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "bugs"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    "url"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"https://github.com/Comandeer/czasomierze/issues"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  },</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "homepage"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"https://github.com/Comandeer/czasomierze#readme"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Wprowadzimy do niego jednak kilka modyfikacji. Po pierwsze, pole <code>type</code> zmienimy z <code>commonjs</code> na <code>module</code>, a pole <code>main</code> zastąpimy <a href="https://nodejs.org/api/packages.html#exports" rel="noreferrer noopener">polem <code>exports</code></a>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JSON</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>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    "exports"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">        "."</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">            "types"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"./dist/index.d.ts"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">            "import"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"./dist/index.js"</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 style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><div class="note" role="note" aria-labelledby="note-76"><p class="note__label" id="note-76">Dygresja</p><div class="note__content"><p>Dzięki tym zmianom informujemy npm oraz Node.js, że nasz pakiet jest w formacie ESM. Co to dokładnie oznacza, opiszę w jednych z kolejnych artykułów.</p></div></div>
<p>Dodamy też pole <code>files</code>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JSON</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>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    "files"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">    	"dist"</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>Dzięki niemu przy publikacji pakietu zostaną opublikowane tylko pliki z katalogu <code>dist</code> – a więc skompilowana wersja naszej biblioteki.</p>
<div class="note" role="note" aria-labelledby="note-77"><p class="note__label" id="note-77">Dygresja</p><div class="note__content"><p>Tak, publikacji pakietu również&nbsp;będzie poświęcony osobny artykuł.</p></div></div>
<p>A jak już mowa o skompilowanej wersji biblioteki mowa, to warto od razu <a href="https://docs.npmjs.com/cli/v12/commands/npm-install" rel="noreferrer noopener">zainstalować</a> transpiler TypeScripta:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">Shell</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">npm</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> i</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -D</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> typescript</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Ta komenda zainstaluje najnowszą&nbsp;wersję TS-a i doda ją do zależności developerskich projektu. Potrzebujemy jeszcze skryptu, dzięki któremu zbudujemy naszą bibliotekę:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JSON</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>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">	"scripts"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">		"build"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"tsc"</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>Dzięki temu będziemy mogli wywołać komendę <code>npm run build</code>, która stworzy z naszych plików TS pliki JS.</p>
<p>Warto także dodać autora (<code>author</code>), słowa kluczowe (<code>keywords</code>) czy opis (<code>description</code>). Osobiście zmieniam także wersję na niższą, jak <code>0.0.1</code> czy <code>0.1.0</code>. Pierwszą&nbsp;wersję&nbsp;bezpieczniej opublikować z takim <em>eksperymentalnym</em> numerem, zamiast psuć&nbsp;sobie od razu <em>poważną</em> wersję&nbsp;<code>1.0.0</code>, gdyby coś poszło nie tak.</p>
<p>Ostatecznie nasz plik <code>package.json</code> powinien wyglądać mniej więcej tak:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JSON</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>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "name"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"czasomierze"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "version"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"0.0.1"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "description"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"A simpler and friendler timers."</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "type"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"module"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "exports"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    "."</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">      "types"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"./dist/index.d.ts"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">      "import"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"./dist/index.js"</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 style="color:#005CC5;--shiki-dark:#79B8FF">  "files"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">    "dist"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  ],</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "scripts"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    "build"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"tsc"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  },</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "repository"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    "type"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"git"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    "url"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"git+https://github.com/Comandeer/czasomierze.git"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  },</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "keywords"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">    "timers"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  ],</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "author"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Comandeer"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "license"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"MIT"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "bugs"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    "url"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"https://github.com/Comandeer/czasomierze/issues"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  },</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "homepage"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"https://github.com/Comandeer/czasomierze#readme"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "devDependencies"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    "typescript"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"7.0.2"</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><h3 id="package-lockjson"><a class="header-anchor" href="https://blog.comandeer.pl/pakujac-manatki#package-lockjson"><code>package-lock.json</code></a></h3>
<p>Po instalacji TypeScriptu powinien pojawić się plik <code>package-lock.json</code>. To plik zawierający dokładne informacje o zainstalowanych zależnościach i wersjach. Na ten moment nie musimy się nim martwić, wrócimy do niego w artykule o zależnościach.</p>
<h3 id="tsconfigjson"><a class="header-anchor" href="https://blog.comandeer.pl/pakujac-manatki#tsconfigjson"><code>tsconfig.json</code></a></h3>
<p>Żeby móc transpilować naszą&nbsp;bibliotekę, przyda się&nbsp;także <a href="https://www.typescriptlang.org/tsconfig/" rel="noreferrer noopener">konfiguracja TypeScriptu</a>, którą zapiszemy w pliku <code>tsconfig.json</code>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JSON</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>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">	"include"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">		"src/**/*"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	],</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">	"compilerOptions"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">		"rootDir"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"./src"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">		"outDir"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"dist/"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">		"module"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"nodenext"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">		"moduleResolution"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"nodenext"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">		"resolveJsonModule"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">		"target"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"esnext"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">		"declaration"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">		"declarationMap"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">		"sourceMap"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">		"skipLibCheck"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">		"verbatimModuleSyntax"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">		"allowImportingTsExtensions"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">		"rewriteRelativeImportExtensions"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">		"strict"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">		"strictNullChecks"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">		"exactOptionalPropertyTypes"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">		"noImplicitOverride"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">		"noUncheckedIndexedAccess"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">		"noFallthroughCasesInSwitch"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">		"noImplicitReturns"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">		"noPropertyAccessFromIndexSignature"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</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>Nie będę wchodził w szczegóły – to zdecydowanie temat na osobny artykuł. Wypada jedynie wspomnieć, że pole <code>include</code> zawiera listę plików, które mają zostać stranspilowane, natomiast opcja <code>compilerOptions.outDir</code> – ścieżkę do katalogu, w którym mają&nbsp;być zapisane wygenerowane pliki JS.</p>
<h3 id="srcindexts"><a class="header-anchor" href="https://blog.comandeer.pl/pakujac-manatki#srcindexts"><code>src/index.ts</code></a></h3>
<p>To plik zawierający kod naszych czasomierzy! To <a href="https://blog.comandeer.pl/projektujemy-czasomierze#ca%C5%82o%C5%9B%C4%87-kodu">niemal ten sam kod</a>, co w poprzednim artykule:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">TypeScript</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">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Hours</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }h`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Minutes</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }m`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Seconds</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }s`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> MiliSeconds</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }ms`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Delay</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	|</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	|</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Exclude</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">		`${</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Hours</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ''}${</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Minutes</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ''</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }${</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Seconds</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ''</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }${</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> MiliSeconds</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ''}`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">		''</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&gt;;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">interface</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> SetTimeoutOptions</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	signal</span><span style="color:#D73A49;--shiki-dark:#F97583">?:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> AbortSignal</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">export</span><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:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	delay</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Delay</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	{ </span><span style="color:#E36209;--shiki-dark:#FFAB70">signal</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> SetTimeoutOptions</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Promise</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; {</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:#E36209;--shiki-dark:#FFAB70">reject</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">		if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( signal?.aborted ) {</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">			reject</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( signal.reason );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">			return</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">		const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> delayInMs</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> convertDelayToMs</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( delay );</span></span>
<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:#24292E;--shiki-dark:#E1E4E8"> globalThis.</span><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:#6F42C1;--shiki-dark:#B392F0">			resolve</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( Date.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">now</span><span style="color:#24292E;--shiki-dark:#E1E4E8">() );</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		}, delayInMs );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( signal </span><span style="color:#D73A49;--shiki-dark:#F97583">!==</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">			signal.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">addEventListener</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'abort'</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:#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">				reject</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( signal.reason );</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 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">export</span><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"> tick</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:#E36209;--shiki-dark:#FFAB70">	tick</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Delay</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	options</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> SetTimeoutOptions</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> AsyncIterableIterator</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	while</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		yield</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> wait</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( tick, options );</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>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">interface</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> DelayGroups</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	hours</span><span style="color:#D73A49;--shiki-dark:#F97583">?:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Hours</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	minutes</span><span style="color:#D73A49;--shiki-dark:#F97583">?:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Minutes</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	seconds</span><span style="color:#D73A49;--shiki-dark:#F97583">?:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Seconds</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	miliseconds</span><span style="color:#D73A49;--shiki-dark:#F97583">?:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> MiliSeconds</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"> convertDelayToMs</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">delay</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Delay</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> )</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( </span><span style="color:#D73A49;--shiki-dark:#F97583">typeof</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> delay </span><span style="color:#D73A49;--shiki-dark:#F97583">===</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'number'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> delay;</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"> delayRegex</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> /</span><span style="color:#D73A49;--shiki-dark:#F97583">^</span><span style="color:#032F62;--shiki-dark:#DBEDFF">(?&lt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">hours</span><span style="color:#032F62;--shiki-dark:#DBEDFF">&gt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">\d</span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#032F62;--shiki-dark:#DBEDFF">h)</span><span style="color:#D73A49;--shiki-dark:#F97583">?</span><span style="color:#032F62;--shiki-dark:#DBEDFF">(?&lt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">minutes</span><span style="color:#032F62;--shiki-dark:#DBEDFF">&gt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">\d</span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#032F62;--shiki-dark:#DBEDFF">m(?!s))</span><span style="color:#D73A49;--shiki-dark:#F97583">?</span><span style="color:#032F62;--shiki-dark:#DBEDFF">(?&lt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">seconds</span><span style="color:#032F62;--shiki-dark:#DBEDFF">&gt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">\d</span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#032F62;--shiki-dark:#DBEDFF">s)</span><span style="color:#D73A49;--shiki-dark:#F97583">?</span><span style="color:#032F62;--shiki-dark:#DBEDFF">(?&lt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">miliseconds</span><span style="color:#032F62;--shiki-dark:#DBEDFF">&gt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">\d</span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#032F62;--shiki-dark:#DBEDFF">ms)</span><span style="color:#D73A49;--shiki-dark:#F97583">?$</span><span style="color:#032F62;--shiki-dark:#9ECBFF">/</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"> matched</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> delay.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">match</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( delayRegex );</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">hours</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">minutes</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">seconds</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">miliseconds</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> } </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> matched</span><span style="color:#D73A49;--shiki-dark:#F97583">!</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.groups </span><span style="color:#D73A49;--shiki-dark:#F97583">as</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> DelayGroups</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	let</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> totalDelay </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( hours </span><span style="color:#D73A49;--shiki-dark:#F97583">!==</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		totalDelay </span><span style="color:#D73A49;--shiki-dark:#F97583">+=</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> removeUnit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( hours ) </span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 3600000</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">	if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( minutes </span><span style="color:#D73A49;--shiki-dark:#F97583">!==</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		totalDelay </span><span style="color:#D73A49;--shiki-dark:#F97583">+=</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> removeUnit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( minutes ) </span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 60000</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">	if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( seconds </span><span style="color:#D73A49;--shiki-dark:#F97583">!==</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		totalDelay </span><span style="color:#D73A49;--shiki-dark:#F97583">+=</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> removeUnit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( seconds ) </span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1000</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">	if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( miliseconds </span><span style="color:#D73A49;--shiki-dark:#F97583">!==</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		totalDelay </span><span style="color:#D73A49;--shiki-dark:#F97583">+=</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> removeUnit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( miliseconds );</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">	return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> totalDelay;</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"> removeUnit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">delay</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Hours</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Minutes</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Seconds</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> MiliSeconds</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> )</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	return</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Number</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( delay.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">replaceAll</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> /</span><span style="color:#005CC5;--shiki-dark:#79B8FF">[a-z]</span><span style="color:#032F62;--shiki-dark:#9ECBFF">/</span><span style="color:#D73A49;--shiki-dark:#F97583">g</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">''</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></code></pre>

			</div>
		</figure><p>Pojawiła się w nim tylko jedna, mała zmiana: funkcje <code>wait()</code> (1) i <code>tick()</code> (2) są eksportowane. Dzięki temu, gdy ktoś zainstaluje nasz pakiet, będzie mógł go użyć w swoim kodzie:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">TypeScript</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"> { wait, tick } </span><span style="color:#D73A49;--shiki-dark:#F97583">from</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'czasomierze'</span><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:#6F42C1;--shiki-dark:#B392F0"> wait</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'5s'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">for</span><span style="color:#D73A49;--shiki-dark:#F97583"> await</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( </span><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> _</span><span style="color:#D73A49;--shiki-dark:#F97583"> of</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> tick</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>
<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">'tick'</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></code></pre>

			</div>
		</figure><p>Skoro już mamy plik z kodem, to możemy przy okazji przetestować, czy nasz kod poprawnie się&nbsp;transpiluje. W tym celu wystarczy wywołać polecenie:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">Shell</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">npm</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> run</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> build</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Jeśli wszystko pójdzie dobrze, powinien pojawić się katalog <code>dist</code>, a w nim plik <code>index.js</code> z kodem JS naszej biblioteki.</p>
<h3 id="readmemd"><a class="header-anchor" href="https://blog.comandeer.pl/pakujac-manatki#readmemd"><code>README.md</code></a></h3>
<p>W tym pliku znajduje się opis projektu wraz z podstawową dokumentacją. To jego zawartość&nbsp;widać&nbsp;zarówno na npm-ie, jak i GitHubie. Dobrze więc, żeby był jak najbardziej czytelny i zawierał wszystkie potrzebne informacje. Osobiście stosuję taki szablon:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">Markdown</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:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#79B8FF;--shiki-dark-font-weight:bold"># &lt;</span><span style="color:#22863A;--shiki-light-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold">nazwa</span><span style="color:#6F42C1;--shiki-light-font-weight:bold;--shiki-dark:#B392F0;--shiki-dark-font-weight:bold"> pakietu</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">informacyjne</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> tarcze</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">Opis</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> projektu</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> w</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> jednym</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> zdaniu</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">## Installation</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">```bash</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">npm install &lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">nazwa</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> pakietu</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</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">## Usage</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">Przykład</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> użycia,</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> najlepiej</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> z</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> kodem</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">Dodatkowe</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> sekcje</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> z</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> potrzebnymi</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> informacjami</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">## License</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">See [LICENSE](./LICENSE) file for details.</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Dla czasomierzy plik <code>README.md</code> może wyglądać następująco:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">Markdown</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:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#79B8FF;--shiki-dark-font-weight:bold"># czasomierze</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Simpler and friendler timers.</span></span>
<span class="line"></span>
<span class="line"><span style="color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#79B8FF;--shiki-dark-font-weight:bold">## Installation</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">```bash</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> czasomierze</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">```</span></span>
<span class="line"></span>
<span class="line"><span style="color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#79B8FF;--shiki-dark-font-weight:bold">## Usage</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">```typescript</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { wait, tick } </span><span style="color:#D73A49;--shiki-dark:#F97583">from</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'czasomierze'</span><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:#6F42C1;--shiki-dark:#B392F0"> wait</span><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">// Waits for 5 seconds</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:#032F62;--shiki-dark:#9ECBFF">'10m'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) </span><span style="color:#6A737D;--shiki-dark:#6A737D">// waits 10 minutes</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">for</span><span style="color:#D73A49;--shiki-dark:#F97583"> await</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( </span><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> _</span><span style="color:#D73A49;--shiki-dark:#F97583"> of</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> tick</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'1s'</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">'tick'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// Logs in 1 second intervals</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>
<span class="line"><span style="color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#79B8FF;--shiki-dark-font-weight:bold">## License</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">See [</span><span style="color:#032F62;--shiki-light-text-decoration:underline;--shiki-dark:#DBEDFF;--shiki-dark-text-decoration:underline">LICENSE</span><span style="color:#24292E;--shiki-dark:#E1E4E8">](</span><span style="color:#24292E;--shiki-light-text-decoration:underline;--shiki-dark:#E1E4E8;--shiki-dark-text-decoration:underline">./LICENSE</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) file for details.</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>{% note %}</p>
<p>Choć nie ma obowiązku tworzenia pliku <code>README.md</code> w języku angielskim, to jednak jest on de facto standardem w branży. Jeśli chcemy dodać także informacje w innym języku, można stworzyć dodatkowe pliki <code>README-&lt;kod języka&gt;.md</code>, np. <code>README-pl.md</code> dla języka polskiego.</p>
<p>{% endnote %}</p>
<h3 id="license"><a class="header-anchor" href="https://blog.comandeer.pl/pakujac-manatki#license"><code>LICENSE</code></a></h3>
<p>Ten plik zawiera treść licencji, na której rozprowadzamy nasze oprogramowanie. Osobiście w większości przypadków wybieram <a href="https://opensource.org/license/mit" rel="noreferrer noopener">licencję MIT</a>. Z kolei npm proponuje domyślnie <a href="https://opensource.org/license/isc" rel="noreferrer noopener">licencję ISC</a> – która jest praktycznie tożsama z MIT. Strona <a href="https://choosealicense.com/" rel="noreferrer noopener"><cite lang="en">Choose an open source license</cite></a> zawiera więcej licencji przeznaczonych dla projektów open source.</p>
<p>Licencje open source to oczywiście niejedyny możliwy wybór. Niektóre pakiety npm są na licencjach komercyjnych (wówczas najczęściej nie linkują&nbsp;też&nbsp;do repozytorium z kodem). Coraz popularniejsze robią&nbsp;się&nbsp;także licencje typu <a href="https://en.wikipedia.org/wiki/Source-available_software" rel="noreferrer noopener"><i lang="en">source-available</i></a>, które np. mogą mieć dodatkowe ograniczenia dotyczące komercyjnego wykorzystania kodu.</p>
<h3 id="contributingmd"><a class="header-anchor" href="https://blog.comandeer.pl/pakujac-manatki#contributingmd"><code>CONTRIBUTING.md</code></a></h3>
<p>Ten plik zawiera <a href="https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/setting-guidelines-for-repository-contributors" rel="noreferrer noopener">informacje dla osób, które chcą dołączyć&nbsp;do projektu</a>. Powinien wskazywać, w jaki sposób zakładać nowe <a href="https://docs.github.com/en/issues/tracking-your-work-with-issues/learning-about-issues/quickstart" rel="noreferrer noopener">issues</a>, jakie wymogi muszą spełniać <a href="https://docs.github.com/en/pull-requests/reference/pull-requests" rel="noreferrer noopener">PR-y</a>, jaki styl kodu jest używany w projekcie, itd. Jeśli nasz projekt jest małym, prywatnym projektem, to raczej można się obejść bez tego pliku. Natomiast jeśli chcemy go rozwijać na większą skalę, to taki plik warto dodać prędzej niż później.</p>
<h3 id="changelogmd"><a class="header-anchor" href="https://blog.comandeer.pl/pakujac-manatki#changelogmd"><code>CHANGELOG.md</code></a></h3>
<p>Jeśli ktoś&nbsp;chce się szybko zorientować, co zmieniło się w naszej bibliotece, najczęściej zagląda właśnie do pliku <code>CHANGELOG.md</code>. Ten plik w czytelny sposób powinien opisywać, jakie rzeczy zmieniły się w poszczególnych wersjach. Jest wiele przyjętych formatów tego pliku, ja osobiście stosuję <a href="https://keepachangelog.com/en/1.1.0/" rel="noreferrer noopener"><cite lang="en">Keep a Changelog</cite></a>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">Markdown</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:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#79B8FF;--shiki-dark-font-weight:bold"># czasomierze Changelog</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">All notable changes to this project will be documented in this file.</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">The format is based on [</span><span style="color:#032F62;--shiki-light-text-decoration:underline;--shiki-dark:#DBEDFF;--shiki-dark-text-decoration:underline">Keep a Changelog</span><span style="color:#24292E;--shiki-dark:#E1E4E8">](</span><span style="color:#24292E;--shiki-light-text-decoration:underline;--shiki-dark:#E1E4E8;--shiki-dark-text-decoration:underline">http://keepachangelog.com/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">and this project adheres to [</span><span style="color:#032F62;--shiki-light-text-decoration:underline;--shiki-dark:#DBEDFF;--shiki-dark-text-decoration:underline">Semantic Versioning</span><span style="color:#24292E;--shiki-dark:#E1E4E8">](</span><span style="color:#24292E;--shiki-light-text-decoration:underline;--shiki-dark:#E1E4E8;--shiki-dark-text-decoration:underline">http://semver.org/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">).</span></span>
<span class="line"></span>
<span class="line"><span style="color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#79B8FF;--shiki-dark-font-weight:bold">---</span></span>
<span class="line"></span>
<span class="line"><span style="color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#79B8FF;--shiki-dark-font-weight:bold">## 0.1.0</span></span>
<span class="line"><span style="color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#79B8FF;--shiki-dark-font-weight:bold">### Added</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">*</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> First working version, yay!</span></span>
<span class="line"></span>
<span class="line"></span></code></pre>

			</div>
		</figure><h3 id="github"><a class="header-anchor" href="https://blog.comandeer.pl/pakujac-manatki#github"><code>.github</code></a></h3>
<p>W tym katalogu znajdują się <a href="https://docs.github.com/en/communities" rel="noreferrer noopener">ustawienia <em>społecznościowe</em> GitHuba</a>, pozwalające zmienić np. <a href="https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository" rel="noreferrer noopener">szablony dla nowych issues</a> czy <a href="https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository" rel="noreferrer noopener">szablon dla nowych PR-ów</a>. Można w nim także umieścić wspomniany już plik <code>CONTRIBUTING.md</code>.</p>
<h3 id="editorconfig"><a class="header-anchor" href="https://blog.comandeer.pl/pakujac-manatki#editorconfig"><code>.editorconfig</code></a></h3>
<p>Ten plik zawiera ustawienia dla edytora kodu w formacie <a href="https://editorconfig.org/" rel="noreferrer noopener">EditorConfig</a>. Format ten jest rozpoznawany przez wiele różnych edytorów (niektóre wymagają rozszerzeń, jak np. <a href="https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig" rel="noreferrer noopener">VSC</a>). Dzięki niemu można ustawić m.in. preferowany styl nowych linii czy szerokość i styl wcięć.  Mój standardowy plik <code>.editorconfig</code> wygląda następująco:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">INI</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">root</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> = true</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Unix-style newlines with a newline ending every file</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">[*]</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">end_of_line</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> = lf</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">insert_final_newline</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> = true</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">indent_style</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> = tab</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">indent_size</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> = 4</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">[*.yml]</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">indent_style</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> = space</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">indent_size</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> = 2</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">[package.json]</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">indent_style</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> = space</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">indent_size</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> = 2</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><h3 id="nvmrc"><a class="header-anchor" href="https://blog.comandeer.pl/pakujac-manatki#nvmrc"><code>.nvmrc</code></a></h3>
<p>Jeśli mamy zainstalowaną tylko jedną wersję Node’a na komputerze, ten plik nie jest nam do niczego potrzebny. Niemniej, gdy pracuje się nad kilkoma projektami równocześnie, bardzo szybko się okazuje, że różne projekty potrzebują&nbsp;różnych wersji Node. I tutaj na pomoc przychodzą managery wersji Node’a. Osobiście stosuję <a href="https://github.com/nvm-sh/nvm" rel="noreferrer noopener">nvm</a>, stąd plik konfiguracyjny <code>.nvmrc</code>. W przypadku naszego projektu można skonfigurować nvm, żeby zawsze używał najnowszej dostępnej wersji Node’a:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage"></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>node</span></span>
<span class="line"><span></span></span></code></pre>

			</div>
		</figure><h3 id="gitattributes"><a class="header-anchor" href="https://blog.comandeer.pl/pakujac-manatki#gitattributes"><code>.gitattributes</code></a></h3>
<p><a href="https://git-scm.com/docs/gitattributes" rel="noreferrer noopener">Plik <code>.giattributes</code></a> ustawia atrybuty dla wybranych plików. Na ten moment nie będziemy się wgłębiać, co to dokładnie oznacza – na to przyjdzie czas w osobnym artykule. Na potrzeby naszego projektu starczy nam taki plik <code>.gitattributes</code>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage"></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>* text=auto</span></span>
<span class="line"><span>*.js eol=lf</span></span>
<span class="line"><span>*.ts eol=lf</span></span>
<span class="line"><span></span></span></code></pre>

			</div>
		</figure><h3 id="podsumowanie"><a class="header-anchor" href="https://blog.comandeer.pl/pakujac-manatki#podsumowanie">Podsumowanie</a></h3>
<p>Nasz szkielet powinien wyglądać mniej więcej tak:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage"></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>| - .github/</span></span>
<span class="line"><span>|   | - ISSUE_TEMPLATE.md</span></span>
<span class="line"><span>|   | - PULL_REQUEST_TEMPLATE.md</span></span>
<span class="line"><span>| - src/</span></span>
<span class="line"><span>|   | - index.ts</span></span>
<span class="line"><span>| - .editorconfig</span></span>
<span class="line"><span>| - .gitattributes</span></span>
<span class="line"><span>| - .gitignore</span></span>
<span class="line"><span>| - .nvmrc</span></span>
<span class="line"><span>| - CHANGELOG.md</span></span>
<span class="line"><span>| - CONTRIBUTING.md</span></span>
<span class="line"><span>| - LICENSE</span></span>
<span class="line"><span>| - package-lock.json</span></span>
<span class="line"><span>| - package.json</span></span>
<span class="line"><span>| - README.md</span></span>
<span class="line"><span>| - tsconfig.json</span></span>
<span class="line"><span></span></span></code></pre>

			</div>
		</figure><p>Możemy go teraz <a href="https://github.com/git-guides/git-commit" rel="noreferrer noopener">scomiitować</a> i <a href="https://github.com/git-guides/git-push" rel="noreferrer noopener">wypchnąć do repozytorium</a>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">Shell</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">git</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> add</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> .</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> &amp;&amp; </span><span style="color:#6F42C1;--shiki-dark:#B392F0">git</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> commit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -m</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "chore: Add skeleton"</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> &amp;&amp; </span><span style="color:#6F42C1;--shiki-dark:#B392F0">git</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> push</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> origin</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> main</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><div class="note" role="note" aria-labelledby="note-78"><p class="note__label" id="note-78">Dygresja</p><div class="note__content"><p>O śmiesznym formacie commitów (<code>chore: &lt;treść&gt;</code>) porozmawiamy kiedy indziej!</p></div></div>
<p>I tym oto sposobem <a href="https://github.com/Comandeer/czasomierze" rel="noreferrer noopener">nasze repozytorium</a> zawiera działający zalążek pakietu npm!</p>
<h2 id="co-dalej"><a class="header-anchor" href="https://blog.comandeer.pl/pakujac-manatki#co-dalej">Co dalej?</a></h2>
<p>Postawiliśmy repozytorium na GitHubie i ogarnęliśmy szkielet pakietu npm. Dalszą pracę rozbijemy na zdecydowanie mniejsze etapy (totalnie nie po to, żeby łatwiej mi się pisało posty…). W następnych odcinkach przyglądniemy się bliżej niektórym plikom w repozytorium, dodamy testy i lintery, a na końcu – opublikujemy paczkę w npmie!</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Projektujemy czasomierze</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/projektujemy-czasomierze" rel="alternate" type="text/html"/>
			<published>2025-03-10T14:58:00.000Z</published>
			<updated>2025-03-10T14:58:00.000Z</updated>
			<id>https://blog.comandeer.pl/projektujemy-czasomierze</id>
			
				<summary><![CDATA[Zaprojektujmy czasomierze lepsze od tych w przeglądarce]]></summary>
			
			<content type="html"><![CDATA[<p><a href="https://blog.comandeer.pl/tik-tak">W poprzednim odcinku</a> przyjrzeliśmy się, jak działają czasomierze w przeglądarce i zidentyfikowaliśmy kilka problemów, które można poprawić. Dzisiaj spróbujemy zaprojektować <s>lepsze</s> nowe API czasomierzy!</p>
<h2 id="jak-to-będziemy-robić"><a class="header-anchor" href="https://blog.comandeer.pl/projektujemy-czasomierze#jak-to-będziemy-robić">Jak to będziemy robić?</a></h2>
<p>Zajmiemy się wszystkimi wspomnianymi problemami po kolei, próbując ich rozwiązania zamknąć w spójne, sensowne API. Będziemy korzystać z już istniejących rozwiązań, żeby podpatrzeć, jak można pewne rzeczy zrobić. Będziemy też całość pisać w <a href="https://www.typescriptlang.org/" rel="noreferrer noopener">TypeScripcie</a>, dzięki czemu za darmo dostaniemy ładne typy. Oficjalna strona TypeScriptu udostępnia <a href="https://www.typescriptlang.org/play/" rel="noreferrer noopener">miejsce do testowania kodu online</a>.</p>
<div class="note" role="note" aria-labelledby="note-69"><p class="note__label" id="note-69">Dygresja</p><div class="note__content"><p>Część przykładów z tego artykułu może nie chcieć się uruchomić we wspomnianym wyżej narzędziu, narzekając na obecność&nbsp;<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>. W takim wypadku wystarczy wymusić traktowanie kodu jako modułu ES poprzez dodanie, czy to na początku, czy na końcu, pustego eksportu (<code>export {}</code>) lub otoczyć całość w <a href="https://developer.mozilla.org/en-US/docs/Glossary/IIFE" rel="noreferrer noopener">samowywołującą się asynchroniczną funkcję</a>.</p></div></div>
<p>Obecnie kod TS można także <a href="https://nodejs.org/en/learn/typescript/run-natively" rel="noreferrer noopener">uruchomić bezpośrednio w Node.js</a>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">Shell</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">$</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> node</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --experimental-strip-types</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ./nasz-plik.ts</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Node.js obsługuje TS-a od wersji 22.6.0.</p>
<h2 id="poprawiamy-asynchroniczność"><a class="header-anchor" href="https://blog.comandeer.pl/projektujemy-czasomierze#poprawiamy-asynchroniczność">Poprawiamy asynchroniczność</a></h2>
<p>Pierwszym problemem było <a href="https://blog.comandeer.pl/tik-tak#przestarza%C5%82e-podej%C5%9Bcie-do-asynchroniczno%C5%9Bci">przestarzałe podejście do asynchroniczności</a>, objawiające się&nbsp;używaniem <a href="https://developer.mozilla.org/en-US/docs/Glossary/Callback_function" rel="noreferrer noopener">callbacków</a>. Dlatego nasze API powinno używać <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise" rel="noreferrer noopener">obietnic</a>, które obecnie są zalecanym sposobem obsługi asynchroniczności.</p>
<p>Przepisywanie APi callbackowego na obietnicowe najlepiej podpatrzyć w Node.js, które miało kiedyś ten problem:</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>
<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>
<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>
<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>
<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>
<span class="line"></span></code></pre>

			</div>
		</figure><p>W przypadku callbackowego API z parametrów wyleciał callback (zatem <code>readFilePromise()</code> przyjmuje tylko 2 parametry, podczas gdy <code>readFileCallback()</code> – 3). Wynik działania funkcji za to jest zwracany z tej funkcji, nie zaś – przekazywany do callbacku. I to w sumie tyle, ile będzie nam potrzebne. Warto jednak wspomnieć, że zmienia się&nbsp;także sposób obsługi błędów:</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">readFileCallback</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'/plik-do-ktorego-nie-mamy-uprawnien'</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">// 1</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>
<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"> 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">'/plik-do-ktorego-nie-mamy-uprawnien'</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>
<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></code></pre>

			</div>
		</figure><p>W przypadku, gdy wystąpi błąd w wersji callbackowej, zostanie on przekazany jako pierwszy parametr do callbacku (1). W przypadku wersji z obietnicą, błąd spowoduje odrzucenie obietnicy. Można go obsłużyć, dodając blok <code>try</code>/<code>catch</code> (2).</p>
<p>Niemniej przy czasomierzach nie będziemy dodawać obsługi błędów. To API bowiem nie rzuca żadnymi błędami.</p>
<p>Mając to wszystko na uwadze, stwórzmy obietnicowe <code>setTimeout()</code>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">TypeScript</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">async</span><span style="color:#D73A49;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> setTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">delay</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> )</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Promise</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">void</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</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">// 2</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		globalThis.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">setTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( resolve, delay ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</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 asynchroniczną funkcję&nbsp;<code>setTimeout()</code> (1), która przyjmuje jako parametr <code>delay</code>. Ten parametr jest typu <code>number</code>, bo przekazywać&nbsp;będziemy liczbę milisekund. Dodatkowo oznaczamy funkcję jako zwracającą pustą obietnicę (<code>Promise&lt;void&gt;</code>). W środku tworzymy nową obietnicę (2), w której wywołujemy <code>globalThis.setTimeout()</code> (3), a następnie zwracamy tę obietnicę. Do wywołania <code>globalThis.setTimeout()</code> przekazujemy <code>resolve()</code> obietnicy jako callback oraz <code>delay</code> jako liczbę milisekund do odczekania.</p>
<p>Kod ten wygląda bardzo podobnie do funkcji <code>wait()</code> z poprzedniego artykułu. Niemniej, z uwagi na to, że używamy nazwy <code>setTimeout()</code>, tym samym przesłaniamy natywną&nbsp;funkcję <code>setTimeout()</code>. Ta jednak wciąż istnieje jako <code>globalThis.setTimeout()</code> – a więc funkcja globalna o takiej nazwie. <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis" rel="noreferrer noopener">Zmienna <code>globalThis</code></a> wskazuje na globalne <code>this</code> (w uproszczeniu można przyjąć, że globalny obiekt) i jest dostępna nie tylko w przeglądarce, ale także w innych środowiskach uruchomieniowych JS-a (takich jak Node.js).</p>
<p>Tym sposobem mamy rozwiązaną połowę problemu asynchroniczności. Ale pozostaje jeszcze <code>setInterval()</code>, które służy do wykonywania jakiejś&nbsp;czynności co określony czas. Nie jesteśmy w stanie zatem zwrócić pojedynczej obietnicy. Na szczęście ktoś już ten problem rozwiązał! W Node.js istnieje oparte na obietnicach API czasomierzy, w tym – <a href="https://nodejs.org/api/timers.html#timerspromisessetintervaldelay-value-options" rel="noreferrer noopener">takie dla <code>setInterval()</code></a>. API to zwraca nie obietnicę, a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_async_iterator_and_async_iterable_protocols" rel="noreferrer noopener">asynchroniczny iterator</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"> { setInterval } </span><span style="color:#D73A49;--shiki-dark:#F97583">from</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'node:timers/promises'</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:#D73A49;--shiki-dark:#F97583">for</span><span style="color:#D73A49;--shiki-dark:#F97583"> await</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( </span><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> _</span><span style="color:#D73A49;--shiki-dark:#F97583"> of</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> setInterval</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:#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">'tick'</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">}</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Importujemy obietnicową wersję <code>setInterval()</code> z modułu <code>node:timers/promises</code> (1). Następnie wywołujemy <code>setInterval()</code> wewnątrz <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of" rel="noreferrer noopener">pętli <code>for await...of</code></a> (2). Co sekundę pętla ta spowoduje wyświetlenie słowa <code>'tick'</code> (3).</p>
<p>Spróbujmy zatem odtworzyć&nbsp;to API:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">TypeScript</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">async</span><span style="color:#D73A49;--shiki-dark:#F97583"> function*</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> setInterval</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">tick</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> )</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> AsyncIterableIterator</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">void</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	while</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</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">		yield</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> setTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( tick ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</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>
<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"> setTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">delay</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> )</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Promise</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">void</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; {</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>Najprostszym sposobem na stworzenie asynchronicznego iteratora jest wykorzystanie <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function*" rel="noreferrer noopener">asynchronicznego generatora</a> (1). Wewnątrz niego tworzymy nieskończoną pętlę (2), natomiast w niej – <code>yield</code>ujemy wywołanie stworzonego przez nas wcześniej<code>setTimeout()</code> (3). Tym sposobem za każdym razem zwracamy obietnicę, którą zewnętrzny kod może wykorzystać w pętli <code>for await...for</code>.</p>
<p>Dość ironicznie wewnątrz naszego <code>setInterval()</code> nie da się wykorzystać&nbsp;tego oryginalnego. Wynika to stąd, że dla każdego “tyknięcia” musimy zwrócić osobną wartość (nową obietnicę). W przypadku natywnego <code>setInterval()</code> byłoby to niemożliwe. W przeciwieństwie do <code>setTimeout()</code>, przekazanie <code>resolve()</code> nie zadziałałoby poprawnie (nie da się spełnić dwa razy tej samej obietnicy). Dlatego najłatwiejszym sposobem jest użycie obietnicowej wersji <code>setTimeout()</code>. Dzięki temu za każdym razem zwrócimy nową obietnicę, która zostanie rozwiązana po określonym czasie. Taka “podmiana” pojedynczego odliczania z interwałem jest możliwa, ponieważ z zewnątrz następujące po sobie wywołania <code>setTimeout()</code> tak naprawdę nie różnią się zachowaniem od interwału.</p>
<h2 id="dodanie-wsparcia-dla-abortcontrollera"><a class="header-anchor" href="https://blog.comandeer.pl/projektujemy-czasomierze#dodanie-wsparcia-dla-abortcontrollera">Dodanie wsparcia dla <code>AbortController</code>a</a></h2>
<p>Przejście na obietnicowe API sprawia, że drugi problem, <a href="https://blog.comandeer.pl/tik-tak#brak-wsp%C3%B3%C5%82pracy-z-abortcontrollerem">brak wsparcia dla <code>AbortController</code>a</a>, jest jeszcze bardziej palący. Z racji tego, że obietnica jest tworzona wewnątrz <code>setTimeout()</code>, nie ma za bardzo możliwości jej odrzucenia z zewnątrz. Tutaj bardzo pomogłaby możliwość przekazania sygnału, który mógłby przerywać&nbsp;odliczanie lub interwał. Dorzućmy zatem jego obsługę do naszego API. Zacznijmy od <code>setTimeout()</code>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">TypeScript</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">interface</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> SetTimeoutOptions</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:#E36209;--shiki-dark:#FFAB70">	signal</span><span style="color:#D73A49;--shiki-dark:#F97583">?:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> AbortSignal</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">}</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"> setTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	delay</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	{ </span><span style="color:#E36209;--shiki-dark:#FFAB70">signal</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> SetTimeoutOptions</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</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">)</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Promise</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">void</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; {</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:#E36209;--shiki-dark:#FFAB70">reject</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"> timeoutId</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> globalThis.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">setTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( resolve, delay ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( signal </span><span style="color:#D73A49;--shiki-dark:#F97583">!==</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> undefined</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">			signal.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">addEventListener</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'abort'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, ( </span><span style="color:#E36209;--shiki-dark:#FFAB70">evt</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">				clearTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( timeoutId ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 6</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">				reject</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( evt ); </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 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>W funkcji <code>setTimeout()</code> pojawił się drugi, opcjonalny parametr (1). Korzystam tutaj ze <a href="https://michaelnthiessen.com/options-object-pattern" rel="noreferrer noopener">wzorca obiektu opcji</a>, żeby przekazać do funkcji obiekt z opcjonalną konfiguracją. Kształt opcji jest opisywany przez interfejs <code>SetTimeoutOptions</code> (2). Jak na razie zezwalamy tylko na jedną opcję – <code>signal</code> (3), która będzie naszym <a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal" rel="noreferrer noopener"><code>AbortSignal</code>em</a>. Z kolei w samym <code>setTimeout()</code> zapisujemy id zwracane przez natywne <code>setTimeout()</code> do zmiennej <code>timeoutId</code> (3). Następnie, jeśli sygnał został przekazany (4), dodajemy obsługę jego <a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/abort_event" rel="noreferrer noopener">zdarzenia <code>abort</code></a> (5). Gdy to zdarzenie nastąpi, chcemy anulować odliczanie przy pomocy <code>clearTimeout()</code> (6) oraz odrzucić obietnicę (7). Jako powód odrzucenia zwracamy zdarzenie <code>abort</code>.</p>
<p>Sprawdźmy, czy faktycznie da się w taki sposób zatrzymać odliczanie. Stwórzmy sobie kilka odliczeń korzystających z tego samego sygnału, a następnie wyślijmy do niego informację o anulowaniu asynchronicznej operacji przy pomocy <a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort" rel="noreferrer noopener"><code>AbortController#abort()</code></a>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">TypeScript</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"> 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">// 1</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">// 2</span></span>
<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 style="color:#6A737D;--shiki-dark:#6A737D">// 3</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:#005CC5;--shiki-dark:#79B8FF">1000</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, { signal } ),</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:#005CC5;--shiki-dark:#79B8FF">2000</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, { signal } ),</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:#005CC5;--shiki-dark:#79B8FF">3000</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, { signal } )</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">abortController.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">abort</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 4</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"> results</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><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">allSettled</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( timeouts ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 5</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">results.</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">result</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">( result ); </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></code></pre>

			</div>
		</figure><p>Na początku tworzymy <code>AbortController</code> (1) i wyciągamy z niego sygnał (2). Następnie tworzymy tablicę z <code>setTimeout()</code>ami (3). Każdemu odliczaniu przekazujemy ten sam sygnał. Następnie wywołujemy <code>abortController.abort()</code> (4), żeby przerwać odliczanie. Dalej czekamy na zakończenie wszystkich obietnic przy pomocy <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled" rel="noreferrer noopener"><code>Promise.allSettled()</code></a> (5). Użycie tej funkcji pozwoli nam na zaczekanie zarówno na odrzucone obietnice, jak i na te poprawnie spełnione (w przeciwieństwie do <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all" rel="noreferrer noopener"><code>Promise.all()</code></a>, które obsłuży tylko spełnione obietnice). Status wszystkich obietnic zapisujemy do zmiennej <code>results</code>. Następnie wyświetlamy status każdej obietnicy (6).</p>
<p>Po uruchomieniu powyższego kodu powinniśmy zobaczyć, że dla każdej obietnicy został zwrócony obiekt podobny do poniższego:</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>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">	status</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'rejected'</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:#6F42C1;--shiki-dark:#B392F0">	reason</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">		type</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'abort'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">		defaultPrevented</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">false</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">		cancelable</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">false</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">		timeStamp</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">89.556842</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>Obietnice zostały odrzucone (1), a jako powód odrzucenia podane jest zdarzenie <code>abort</code> (2).</p>
<p>Niemniej natywne API korzystające z <code>AbortSignal</code>a zachowują się nieco inaczej. Przyjrzyjmy się, jak to robi <code>setTimeout</code> z Node’owego modułu <code>node:timers/promises</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">import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { setTimeout } </span><span style="color:#D73A49;--shiki-dark:#F97583">from</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'node:timers/promises'</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:#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">// 2</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">// 3</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> res</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">( </span><span style="color:#005CC5;--shiki-dark:#79B8FF">100</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'result'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, { signal } ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 4</span></span>
<span class="line"></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:#6A737D;--shiki-dark:#6A737D">// 5</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">await</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> res; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 6</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Na początku importujemy <code>setTimeout()</code> (1). Następnie tworzymy <code>AbortController</code>a (2), wyciągamy z niego sygnał (3) i przekazujemy go do wywołania <code>setTimeout()</code> (4). Potem wywołujemy <code>abortController.abort()</code> (5) i czekamy na odrzucenie odliczania (6). Jeśli uruchomimy ten kod i spojrzymy do konsoli, zobaczymy mniej więcej taki błąd:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage"></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>node:timers/promises:48</span></span>
<span class="line"><span>    reject(new AbortError(undefined, { cause: signal?.reason }));</span></span>
<span class="line"><span>           ^</span></span>
<span class="line"><span></span></span>
<span class="line"><span>AbortError: The operation was aborted</span></span>
<span class="line"><span>    at Timeout.cancelListenerHandler (node:timers/promises:48:12)</span></span>
<span class="line"><span>    at [nodejs.internal.kHybridDispatch] (node:internal/event_target:816:20)</span></span>
<span class="line"><span>    at AbortSignal.dispatchEvent (node:internal/event_target:751:26)</span></span>
<span class="line"><span>    at runAbort (node:internal/abort_controller:410:10)</span></span>
<span class="line"><span>    at abortSignal (node:internal/abort_controller:396:3)</span></span>
<span class="line"><span>    at AbortController.abort (node:internal/abort_controller:428:5)</span></span>
<span class="line"><span>    at file://[ciach]/test.ts:11:17</span></span>
<span class="line"><span>    ... 2 lines matching cause stack trace ...</span></span>
<span class="line"><span>    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5) {</span></span>
<span class="line"><span>  code: 'ABORT_ERR',</span></span>
<span class="line"><span>  [cause]: DOMException [AbortError]: This operation was aborted</span></span>
<span class="line"><span>      at new DOMException (node:internal/per_context/domexception:53:5)</span></span>
<span class="line"><span>      at AbortController.abort (node:internal/abort_controller:427:18)</span></span>
<span class="line"><span>      at file://[ciach]/test.ts:11:17</span></span>
<span class="line"><span>      at ModuleJob.run (node:internal/modules/esm/module_job:268:25)</span></span>
<span class="line"><span>      at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:543:26)</span></span>
<span class="line"><span>      at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5)</span></span>
<span class="line"><span>}</span></span>
<span class="line"><span></span></span></code></pre>

			</div>
		</figure><p>Node.js rzucił w tym wypadku <code>AbortError</code>, który jako swoją&nbsp;przyczynę (<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause" rel="noreferrer noopener"><code>Error#cause</code></a>) wskazał na przyczynę przerwania przekazaną do sygnału (<a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/reason" rel="noreferrer noopener"><code>AbortSignal#reason</code></a>). Spróbujmy zatem nieco poprawić zachowanie naszego <code>setTimeout()</code>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">TypeScript</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">async</span><span style="color:#D73A49;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> setTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">delay</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, { </span><span style="color:#E36209;--shiki-dark:#FFAB70">signal</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> SetTimeoutOptions</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {} )</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Promise</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">void</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; {</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:#E36209;--shiki-dark:#FFAB70">reject</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"> timeoutId</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> globalThis.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">setTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( resolve, delay );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( signal </span><span style="color:#D73A49;--shiki-dark:#F97583">!==</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">			signal.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">addEventListener</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'abort'</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:#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">				reject</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( signal.reason ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</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 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>Tak naprawdę nie potrzebujemy dodatkowego owijania we własny <code>AbortError</code>, możemy skorzystać bezpośrednio z <code>AbortSignal#reason</code>. Co też robimy (1). I to w sumie jedyna zmiana. Jeśli teraz odpalimy nasz kod, zauważymy, że powód odrzucenia obietnicy się zmienił – teraz jest to błąd typu <a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMException" rel="noreferrer noopener"><code>DOMException</code></a> z komunikatem <code>signal is aborted without reason</code> (testowane w Chrome).</p>
<div class="note" role="note" aria-labelledby="note-70"><p class="note__label" id="note-70">Dygresja</p><div class="note__content"><p>Możemy podać dokładny powód przerwania operacji, przekazując go jako argument do <code>AbortController#abort()</code>. Można tam przekazać dowolną wartość, ale przekazywanie <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error" rel="noreferrer noopener">błędów</a> w takich przypadkach jest w miarę powszechnie stosowaną dobrą praktyką.</p></div></div>
<p>Jest jeszcze jedna sytuacja, którą powinniśmy wziąć pod uwagę: co jeśli do <code>setTimeout()</code> zostanie przekazany już “wykorzystany” sygnał? Może to nastąpić, gdy <code>AbortController#abort()</code> zostało wywołane przed wywołaniem <code>setTimeout()</code>. Obsługa zdarzenia <code>abort</code> tego nie wykryje, bo ono jest w stanie wykryć jedynie przerwanie tu i teraz (tak samo jak np. zdarzenie <code>click</code> nie zareaguje na kliknięcie sprzed 10 minut). Na szczęście istnieje <a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/aborted" rel="noreferrer noopener">własność <code>AbortSignal#aborted</code></a>, która informuje o stanie sygnału:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">TypeScript</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">async</span><span style="color:#D73A49;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> setTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">delay</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, { </span><span style="color:#E36209;--shiki-dark:#FFAB70">signal</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> SetTimeoutOptions</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {} )</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Promise</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">void</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; {</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:#E36209;--shiki-dark:#FFAB70">reject</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">		if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( signal?.aborted ) { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">			reject</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( signal.reason ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">			return</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">		}</span></span>
<span class="line"></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 style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Na samym początku naszej obietnicy dodajemy sprawdzenie, czy własność <code>signal.aborted</code> jest prawdą&nbsp;(1). Stosuję tutaj operator opcjonalnego łańcuchowania (ang. <i lang="en">optional chaining operator</i>) dla zwięzłości zapisu. Jeśli ta własność jest prawdą, odrzucam obietnicę (2) i przerywam dalsze działanie funkcji przez <code>return</code> (3).</p>
<p>Teraz możemy przetestować działanie nowej wersji <code>setTimeout()</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"> 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:#24292E;--shiki-dark:#E1E4E8">abortController.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">abort</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:#005CC5;--shiki-dark:#79B8FF">1000</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, { signal } );</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Na początku tworzymy <code>AbortController</code> (1), wyciągamy z niego sygnał (2), a następnie wywołujemy <code>abortController.abort()</code> (3) i dopiero potem wywołujemy <code>setTimeout()</code> i przekazujemy do niego sygnał (4). Po uruchomieniu tego kodu powinniśmy zobaczyć w konsoli znany już nam błąd <code>DOMException</code>.</p>
<p>Skoro obsługę <code>AbortController</code>a w <code>setTimeout()</code> mamy już za sobą, pora zająć się <code>setInterval()</code>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">TypeScript</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">async</span><span style="color:#D73A49;--shiki-dark:#F97583"> function*</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> setInterval</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	tick</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	options</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> SetTimeoutOptions</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</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">)</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> AsyncIterableIterator</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">void</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	while</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		yield</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> setTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( tick, options ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</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>Zmiana na dobrą&nbsp;sprawę jest jedna. Pojawił się nowy parametr, przyjmujący ten sam obiekt opcji co w przypadku <code>setTimeout()</code> (1). Ten parametr przekazujemy dalej do <code>setTimeout()</code> (2)… i to w sumie tyle. Całą obsługą <code>AbortController</code>a zajmuje się <code>setTimeout()</code>.</p>
<p>Przetestujmy naszą nową wersję <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:#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">// 1</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">// 2</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">// 4</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	for</span><span style="color:#D73A49;--shiki-dark:#F97583"> await</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( </span><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> _</span><span style="color:#D73A49;--shiki-dark:#F97583"> of</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> setInterval</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">, { signal } ) ) { </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">'tick'</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:#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 style="color:#D73A49;--shiki-dark:#F97583">catch</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( e ) {</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">( e.message );</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Standardowo już tworzymy <code>AbortController</code>a (1) i wyciągamy z niego sygnał (2). Następnie tworzymy pętlę <code>for await...of</code> z <code>setInterval()</code>a, do którego przekazaliśmy sygnał (3). Całą pętlę dodatkowo otaczamy blokiem <code>try</code>/<code>catch</code> (4), żeby wyłapać odrzucenie obietnicy. W samej pętli najpierw wyświetlamy <code>'tick'</code> (5), a następnie wywołujemy <code>abortController.abort()</code> (6). Zgodnie z tą logiką powinniśmy zobaczyć w konsoli tylko jeden napis <code>'tick'</code>, a potem – błąd <code>DOMException</code>. I, jeśli wywołamy powyższy kod, to dokładnie tak się stanie!</p>
<h3 id="co-z-cleartimeoutclearinterval"><a class="header-anchor" href="https://blog.comandeer.pl/projektujemy-czasomierze#co-z-cleartimeoutclearinterval">Co z <code>clearTimeout()</code>/<code>clearInterval()</code>?</a></h3>
<p>Obecnie cała nasza obsługa błędów jest oparta o <code>AbortController</code>. Niemniej natywne API posiada dwie funkcje służące do anulowania odliczania i interwałów. Zastanówmy się, czy da się je jakoś zaadaptować na potrzeby nowego API.</p>
<p>Zacznijmy od <code>clearTimeout()</code>. Funkcja ta pozwala anulować odliczanie przy pomocy unikatowego identyfikatora, który jednoznacznie identyfikuje odliczanie. W naszym przypadku również można taki identyfikator znaleźć – może być nim obietnica zwrócona przez <code>setTimeout()</code>. W przypadku <code>clearInterval()</code> można próbować identyfikować przy pomocy asynchronicznego generatora i na tej podstawie anulować poszczególne odliczania. Niemniej uważam, ze jest to niepotrzebne kombinowanie – <code>AbortController</code> dostarcza wygodniejszy i potężniejszy mechanizm anulowania poszczególnych odliczań i interwałów. Dlatego też pozwolę sobie pominąć obydwie te funkcje.</p>
<h2 id="lepszy-format-czasu"><a class="header-anchor" href="https://blog.comandeer.pl/projektujemy-czasomierze#lepszy-format-czasu">Lepszy format czasu</a></h2>
<p>Trzecim wymienionym przeze mnie problemem był <a href="https://blog.comandeer.pl/tik-tak#nieprzyjazny-format-czasu">nieprzyjazny format czasu</a>. Czasomierze akceptują bowiem wyłącznie liczbę milisekund, a ja chciałbym podawać czas w bardziej przystępnym, tekstowym formacie:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage"></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>99h55m33s124ms</span></span>
<span class="line"><span></span></span></code></pre>

			</div>
		</figure><p>Format ten jest luźną adaptacją <a href="https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-duration-string" rel="noreferrer noopener">formatu czasu trwania ze specyfikacji HTML</a>.</p>
<p>Równocześnie jestem w stanie wyobrazić sobie, że część&nbsp;osób będzie go uważać za mniej czytelny od podawania liczby milisekund, więc starszy sposób również powinien być wspierany. Zmieńmy zatem odpowiednio pierwszy parametr naszych czasomierzy:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">TypeScript</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">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Hours</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }h`</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:#D73A49;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Minutes</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }m`</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:#D73A49;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Seconds</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }s`</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:#D73A49;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> MiliSeconds</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }ms`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 9</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Delay</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // 3</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">|</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // 4</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">|</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Exclude</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 6</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">	`${</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Hours</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ''}${</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Minutes</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ''</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }${</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Seconds</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ''</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }${</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> MiliSeconds</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ''}`</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:#032F62;--shiki-dark:#9ECBFF">	''</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;;</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"> setTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	delay</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Delay</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">	{ </span><span style="color:#E36209;--shiki-dark:#FFAB70">signal</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> SetTimeoutOptions</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Promise</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">void</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; {</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>
<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"> setInterval</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	tick</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Delay</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:#E36209;--shiki-dark:#FFAB70">	options</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> SetTimeoutOptions</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> AsyncIterableIterator</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">void</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; {</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>Zarówno w <code>setTimeout()</code> (1), jak i <code>setInterval()</code> (2), typ pierwszego parametru zmienił się z <code>number</code> na <code>Delay</code> (3). Ten typ może przyjąć dwie postaci – albo liczby (4), albo mocno skomplikowanego ciągu znaków (5), który dodatkowo owinięty jest w <code>Exclude&lt;&gt;</code> (6). Zatrzymajmy się na moment przy tym typie.</p>
<p>W TypeScripcie istnieje coś takiego jak <a href="https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html" rel="noreferrer noopener">typy szablonowe literałów</a> (ang. <i lang="en">template literal types</i>). W dużym skrócie są niejako odpowiednikiem <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals" rel="noreferrer noopener">szablonowych ciągów znaków z JS-a</a>. Różnica polega na tym, że gdy w JS-ie możemy w takie ciągi wsadzać zmienne, tak w TS-ie – inne typy. Dlatego też do naszego typu wsadziliśmy typy oznaczające godziny (6), minuty (7), sekundy (8) i milisekundy (9). Jak widać, to też są template literal types, ale prostsze:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">TypeScript</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">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Hours</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }h`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>W tym przypadku typ <code>Hours</code> oznacza ciąg, w którym znajduje się dowolna liczba, po której następuje literka <code>h</code>. Typy dla poszczególnych jednostek czasu wyglądają podobnie, różnią się tylko, cóż, <em>jednostką</em>. Natomiast typ <code>Delay</code> łączy je wszystkie w jeden ciąg – ale każdy człon jest opcjonalny. W końcu nie wszyscy potrzebują czasomierza ustawionego w godzinach. Opcjonalność członów została uzyskana przy pomocy <a href="https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types" rel="noreferrer noopener">unii typów</a>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">TypeScript</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">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Delay</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Hours</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ''</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Każdy człon może zostać zastąpiony przez pusty ciąg znaków. Niemniej to powoduje pewien problem: skoro wszystkie człony są opcjonalne, to ostatecznie typ <code>Delay</code> dopuszcza także pusty ciąg znaków (<code>''</code>). I tutaj przydaje się <a href="https://www.typescriptlang.org/docs/handbook/utility-types.html#excludeuniontype-excludedmembers" rel="noreferrer noopener">typ <code>Exclude&lt;&gt;</code></a>, dzięki któremu odfiltrowujemy z naszego <code>Delay</code> pusty ciąg znaków. Ostatecznie więc otrzymujemy listę wszystkich możliwych kombinacji jednostek czasu.</p>
<p>Innymi słowy, typ <code>Delay</code> można zapisać też jako:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">TypeScript</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">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Delay</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">|</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">|</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}h`</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">|</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}m`</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">|</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}s`</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">|</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}ms`</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">|</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}s${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}ms`</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">|</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}m${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}ms`</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">|</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}m${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}s`</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">|</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}m${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}s${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}ms`</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">|</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}h${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}ms`</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">|</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}h${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}s`</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">|</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}h${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}s${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}ms`</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">|</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}h${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}m`</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">|</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}h${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}m${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}ms`</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">|</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}h${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}m${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}s`</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">|</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}h${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}m${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}s${</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}ms`</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Taką&nbsp;też listę powinniśmy zobaczyć po najechaniu na typ w edytorze kodu/IDE. I choć można by ten typ zapisać tak w kodzie, wydaje mi się, że sposób z <code>Exclude&lt;&gt;</code> + osobnymi typami dla poszczególnych jednostek czasu jest mimo wszystko czytelniejszy. Acz zdecydowanie bardziej <em>sprytny</em> i wymagający nieco przetrawienia.</p>
<p>Skoro mamy już typ parametru, teraz pora go obsłużyć. Z racji tego, że wewnątrz <code>setInterval()</code> jedynie go przekazujemy do wnętrza <code>setTimeout()</code>, zajmijmy się od razu tą drugą funkcją:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">TypeScript</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">async</span><span style="color:#D73A49;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> setTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	delay</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Delay</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	{ </span><span style="color:#E36209;--shiki-dark:#FFAB70">signal</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> SetTimeoutOptions</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Promise</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">void</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; {</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:#E36209;--shiki-dark:#FFAB70">reject</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">		[…]</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"> delayInMs</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> convertDelayToMs</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( delay ); </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"> timeoutId</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> globalThis.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">setTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( resolve, delayInMs ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"></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 style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Pojawiła się nowa zmienna, <code>delayInMs</code>, która przechowuje wynik wywołania funkcji <code>convertDelayToMs()</code> (1). Tę zmienną przekazujemy jako czas do natywnego <code>setTimeout()</code> (2).</p>
<p>Natomiast sama funkcja <code>convertDelayToMs()</code> wygląda następująco:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">TypeScript</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">interface</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> DelayGroups</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 13</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	hours</span><span style="color:#D73A49;--shiki-dark:#F97583">?:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Hours</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	minutes</span><span style="color:#D73A49;--shiki-dark:#F97583">?:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Minutes</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	seconds</span><span style="color:#D73A49;--shiki-dark:#F97583">?:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Seconds</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	miliseconds</span><span style="color:#D73A49;--shiki-dark:#F97583">?:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> MiliSeconds</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"> convertDelayToMs</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">delay</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Delay</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> )</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</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">	if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( </span><span style="color:#D73A49;--shiki-dark:#F97583">typeof</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> delay </span><span style="color:#D73A49;--shiki-dark:#F97583">===</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'number'</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">		return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> delay; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</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"> delayRegex</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> /</span><span style="color:#D73A49;--shiki-dark:#F97583">^</span><span style="color:#032F62;--shiki-dark:#DBEDFF">(?&lt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">hours</span><span style="color:#032F62;--shiki-dark:#DBEDFF">&gt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">\d</span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#032F62;--shiki-dark:#DBEDFF">h)</span><span style="color:#D73A49;--shiki-dark:#F97583">?</span><span style="color:#032F62;--shiki-dark:#DBEDFF">(?&lt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">minutes</span><span style="color:#032F62;--shiki-dark:#DBEDFF">&gt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">\d</span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#032F62;--shiki-dark:#DBEDFF">m(?!s))</span><span style="color:#D73A49;--shiki-dark:#F97583">?</span><span style="color:#032F62;--shiki-dark:#DBEDFF">(?&lt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">seconds</span><span style="color:#032F62;--shiki-dark:#DBEDFF">&gt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">\d</span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#032F62;--shiki-dark:#DBEDFF">s)</span><span style="color:#D73A49;--shiki-dark:#F97583">?</span><span style="color:#032F62;--shiki-dark:#DBEDFF">(?&lt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">miliseconds</span><span style="color:#032F62;--shiki-dark:#DBEDFF">&gt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">\d</span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#032F62;--shiki-dark:#DBEDFF">ms)</span><span style="color:#D73A49;--shiki-dark:#F97583">?$</span><span style="color:#032F62;--shiki-dark:#9ECBFF">/</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:#D73A49;--shiki-dark:#F97583">	const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> matched</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> delay.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">match</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( delayRegex ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 4</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">hours</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">minutes</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">seconds</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">miliseconds</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> } </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> matched.groups </span><span style="color:#D73A49;--shiki-dark:#F97583">as</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> DelayGroups</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:#D73A49;--shiki-dark:#F97583">	let</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> totalDelay </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="color:#24292E;--shiki-dark:#E1E4E8">; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 7</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( hours </span><span style="color:#D73A49;--shiki-dark:#F97583">!==</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> undefined</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">		totalDelay </span><span style="color:#D73A49;--shiki-dark:#F97583">+=</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> removeUnit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( hours ) </span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 3600000</span><span style="color:#24292E;--shiki-dark:#E1E4E8">; </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:#D73A49;--shiki-dark:#F97583">	if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( minutes </span><span style="color:#D73A49;--shiki-dark:#F97583">!==</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		totalDelay </span><span style="color:#D73A49;--shiki-dark:#F97583">+=</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> removeUnit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( minutes ) </span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 60000</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">	if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( seconds </span><span style="color:#D73A49;--shiki-dark:#F97583">!==</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		totalDelay </span><span style="color:#D73A49;--shiki-dark:#F97583">+=</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> removeUnit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( seconds ) </span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1000</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">	if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( miliseconds </span><span style="color:#D73A49;--shiki-dark:#F97583">!==</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		totalDelay </span><span style="color:#D73A49;--shiki-dark:#F97583">+=</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> removeUnit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( miliseconds );</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">	return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> totalDelay; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 12</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"> removeUnit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">delay</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Hours</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Minutes</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Seconds</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> MiliSeconds</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> )</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 10</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	return</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Number</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( delay.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">replaceAll</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> /</span><span style="color:#005CC5;--shiki-dark:#79B8FF">[a-z]</span><span style="color:#032F62;--shiki-dark:#9ECBFF">/</span><span style="color:#D73A49;--shiki-dark:#F97583">g</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">''</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) ); </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></code></pre>

			</div>
		</figure><p>Funkcja <code>convertDelayToMs()</code> przyjmuje jako jedyny parametr wartość <code>delay</code> i zwraca liczbę (1). Na samym początku sprawdzamy, czy przekazane <code>delay</code> jest liczbą (2) – jeśli tak, to po prostu tę wartość zwracamy (3). W innym przypadku parsujemy argument <code>delay</code> (4) przy pomocy wyrażenia regularnego (5). Wyciągamy z rezultatu parsowania <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Named_capturing_group" rel="noreferrer noopener">nazwane grupy</a> (6). Następnie tworzymy zmienną <code>totalDelay</code> (7), która będzie zawierać wynik sumowania czasu. Następnie przechodzimy przez każdą&nbsp;wykrytą jednostkę czasu – sprawdzamy, czy została ustawiona (8), a następnie przeliczamy ją na milisekundy i dodajemy do <code>totalDelay</code> (9). Przy przeliczaniu musimy usunąć samą jednostkę. Do tego służy funkcja <code>removeUnit()</code> (10), która przyjmuje jednostkę czasu i zwraca samą jej wartość. W tym celu usuwa wszelką nieliczbową treść z przekazanej wartości, a rezultat rzutuje na liczbę (11). Gdy skończymy już&nbsp;dodawać poszczególne jednostki czasu do siebie, zwracamy <code>totalDelay</code> (12).</p>
<p>Wróćmy na chwilę do wyrażenia regularnego. Po pierwsze, warto zwrócić uwagę, że nazwane grupy (6) rzutowane są na typ <code>DelayGroups</code> (13). Domyślnie TypeScript nie typuje własności <code>groups</code> w wyniku parsowania ciągu wyrażeniem regularnym, dlatego trzeba to zrobić własnoręcznie. W samym wyrażeniu regularnym z kolei każda jednostka czasu jest oznaczona jako opcjonalna:</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:#032F62;--shiki-dark:#9ECBFF">/</span><span style="color:#032F62;--shiki-dark:#DBEDFF">(?&lt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">hours</span><span style="color:#032F62;--shiki-dark:#DBEDFF">&gt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">\d</span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#032F62;--shiki-dark:#DBEDFF">h)</span><span style="color:#D73A49;--shiki-dark:#F97583">?</span><span style="color:#032F62;--shiki-dark:#9ECBFF">/</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Taki zapis z <code>?</code> za nawiasem oznacza “dana grupa może wystąpić raz lub nie wystąpić wcale”.</p>
<p>Dodatkowo grupa dla minut stosuje tzw. <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Lookahead_assertion" rel="noreferrer noopener">negatywne rozglądnięcie się do przodu</a> (ang. <i lang="en">negative lookahead assertion</i>), żeby przez przypadek nie złapać też milisekund:</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:#032F62;--shiki-dark:#9ECBFF">/</span><span style="color:#032F62;--shiki-dark:#DBEDFF">(?&lt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">minutes</span><span style="color:#032F62;--shiki-dark:#DBEDFF">&gt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">\d</span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#032F62;--shiki-dark:#DBEDFF">m(?!s))</span><span style="color:#D73A49;--shiki-dark:#F97583">?</span><span style="color:#032F62;--shiki-dark:#9ECBFF">/</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Taki zapis oznacza “znajdź liczbę, po której następuje litera <code>m</code>, po której <em>nie</em> następuje litera <code>s</code>”.</p>
<div class="note" role="note" aria-labelledby="note-71"><p class="note__label" id="note-71">Dygresja</p><div class="note__content"><p>Stosując pozytywne rozglądnięcie się do przodu (ang. <i lang="en">positive lookahead assertion</i>) można całe wyrażenie regularne napisać tak, aby wykrywało same wartości liczbowe, przyporządkowując je do konkretnych nazwanych grup na podstawie następujących po nich liter.</p></div></div>
<h2 id="dodatkowe-poprawki"><a class="header-anchor" href="https://blog.comandeer.pl/projektujemy-czasomierze#dodatkowe-poprawki">Dodatkowe poprawki</a></h2>
<p>Nasze API wygląda dobrze. Niemniej myślę, że można dodać jeszcze jedno usprawnienie. Obecnie zarówno <code>setTimeout()</code>, jak i <code>setInterval()</code> nie zwracają żadnej wartości w obietnicach. Zamiast tego mogą zwracać aktualny znacznik czasu (podobnie jak to <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame#timestamp" rel="noreferrer noopener">robi np. <code>requestAnimationFrame()</code></a>):</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">TypeScript</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">async</span><span style="color:#D73A49;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> setTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	delay</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Delay</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	{ </span><span style="color:#E36209;--shiki-dark:#FFAB70">signal</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> SetTimeoutOptions</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Promise</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</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:#E36209;--shiki-dark:#FFAB70">reject</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">		[…]</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"> timeoutId</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> globalThis.</span><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:#6F42C1;--shiki-dark:#B392F0">			resolve</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( Date.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">now</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">		}, delayInMs );</span></span>
<span class="line"></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 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"> setInterval</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	tick</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Delay</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	options</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> SetTimeoutOptions</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> AsyncIterableIterator</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</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>Zmienił się typ zwracanych wartości funkcji – <code>setTimeout()</code> zwraca teraz <code>Promise&lt;number&gt;</code> (1), a <code>setInterval()</code> – <code>AsyncIterableIterator&lt;number&gt;</code> (2). W <code>setTimeout()</code> zaszła jeszcze jedna zmiana – <code>resolve()</code> nie jest już przekazywany bezpośrednio jako callback. Został owinięty w <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions" rel="noreferrer noopener">funkcję strzałkową</a> (3), a do samego <code>resolve()</code> przekazywana jest wartość <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now" rel="noreferrer noopener"><code>Date.now()</code></a> (4).</p>
<div class="note" role="note" aria-labelledby="note-72"><p class="note__label" id="note-72">Dygresja</p><div class="note__content"><p>Aktualny znacznik czasu niekoniecznie jest szczególnie przydatny w przypadku pojedynczego wywołania <code>setTimeout()</code>, ale w przypadku <code>setInterval()</code> może być przydatny do wykorzystania w technice <a href="https://en.wikipedia.org/wiki/Delta_timing" rel="noreferrer noopener"><i lang="en">delta timing</i></a>.</p></div></div>
<p>Kolejnym usprawnieniem, jakie wprowadziłbym, jest… zmiana nazw w naszym API. Istniejące w przeglądarkach czasomierze są na tyle znane, że “podszywanie się” pod nie niekoniecznie jest dobrym pomysłem. Zwłaszcza, że nasze API działa całkowicie inaczej. Nie dość, że zmieniło się zachowanie funkcji <code>setTimeout()</code> i <code>setInterval()</code>, to dodatkowo <code>clearTimeout()</code> i <code>clearInterval()</code> nie działają w ogóle. Dlatego, żeby nie wprowadzać niepotrzebnego chaosu, nasze <code>setTimeout()</code> będzie nazywać się <code>wait()</code>, a <code>setInterval()</code> – <code>tick()</code>.</p>
<h2 id="całość-kodu"><a class="header-anchor" href="https://blog.comandeer.pl/projektujemy-czasomierze#całość-kodu">Całość kodu</a></h2>
<p>Ostatecznie nasz kod powinien wyglądać mniej więcej tak:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">TypeScript</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">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Hours</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }h`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Minutes</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }m`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Seconds</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }s`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> MiliSeconds</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }ms`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Delay</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">|</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">|</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Exclude</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">	`${</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Hours</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ''}${</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Minutes</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ''</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }${</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Seconds</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ''</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }${</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> MiliSeconds</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ''}`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">	''</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">interface</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> SetTimeoutOptions</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	signal</span><span style="color:#D73A49;--shiki-dark:#F97583">?:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> AbortSignal</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">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>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	delay</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Delay</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	{ </span><span style="color:#E36209;--shiki-dark:#FFAB70">signal</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> SetTimeoutOptions</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Promise</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; {</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:#E36209;--shiki-dark:#FFAB70">reject</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">		if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( signal?.aborted ) {</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">			reject</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( signal.reason );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">			return</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">		const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> delayInMs</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> convertDelayToMs</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( delay );</span></span>
<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:#24292E;--shiki-dark:#E1E4E8"> globalThis.</span><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:#6F42C1;--shiki-dark:#B392F0">			resolve</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( Date.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">now</span><span style="color:#24292E;--shiki-dark:#E1E4E8">() );</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		}, delayInMs );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( signal </span><span style="color:#D73A49;--shiki-dark:#F97583">!==</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">			signal.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">addEventListener</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'abort'</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:#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">				reject</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( signal.reason );</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 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">async</span><span style="color:#D73A49;--shiki-dark:#F97583"> function*</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> tick</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	tick</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Delay</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	options</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> SetTimeoutOptions</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> AsyncIterableIterator</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">number</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	while</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		yield</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> wait</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( tick, options );</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>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">interface</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> DelayGroups</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	hours</span><span style="color:#D73A49;--shiki-dark:#F97583">?:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Hours</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	minutes</span><span style="color:#D73A49;--shiki-dark:#F97583">?:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Minutes</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	seconds</span><span style="color:#D73A49;--shiki-dark:#F97583">?:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Seconds</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">	miliseconds</span><span style="color:#D73A49;--shiki-dark:#F97583">?:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> MiliSeconds</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"> convertDelayToMs</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">delay</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Delay</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> )</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( </span><span style="color:#D73A49;--shiki-dark:#F97583">typeof</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> delay </span><span style="color:#D73A49;--shiki-dark:#F97583">===</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'number'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> delay;</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"> delayRegex</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> /</span><span style="color:#D73A49;--shiki-dark:#F97583">^</span><span style="color:#032F62;--shiki-dark:#DBEDFF">(?&lt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">hours</span><span style="color:#032F62;--shiki-dark:#DBEDFF">&gt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">\d</span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#032F62;--shiki-dark:#DBEDFF">h)</span><span style="color:#D73A49;--shiki-dark:#F97583">?</span><span style="color:#032F62;--shiki-dark:#DBEDFF">(?&lt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">minutes</span><span style="color:#032F62;--shiki-dark:#DBEDFF">&gt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">\d</span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#032F62;--shiki-dark:#DBEDFF">m(?!s))</span><span style="color:#D73A49;--shiki-dark:#F97583">?</span><span style="color:#032F62;--shiki-dark:#DBEDFF">(?&lt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">seconds</span><span style="color:#032F62;--shiki-dark:#DBEDFF">&gt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">\d</span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#032F62;--shiki-dark:#DBEDFF">s)</span><span style="color:#D73A49;--shiki-dark:#F97583">?</span><span style="color:#032F62;--shiki-dark:#DBEDFF">(?&lt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">miliseconds</span><span style="color:#032F62;--shiki-dark:#DBEDFF">&gt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">\d</span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#032F62;--shiki-dark:#DBEDFF">ms)</span><span style="color:#D73A49;--shiki-dark:#F97583">?$</span><span style="color:#032F62;--shiki-dark:#9ECBFF">/</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"> matched</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> delay.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">match</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( delayRegex );</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">hours</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">minutes</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">seconds</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">miliseconds</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> } </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> matched.groups </span><span style="color:#D73A49;--shiki-dark:#F97583">as</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> DelayGroups</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	let</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> totalDelay </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( hours </span><span style="color:#D73A49;--shiki-dark:#F97583">!==</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		totalDelay </span><span style="color:#D73A49;--shiki-dark:#F97583">+=</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> removeUnit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( hours ) </span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 3600000</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">	if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( minutes </span><span style="color:#D73A49;--shiki-dark:#F97583">!==</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		totalDelay </span><span style="color:#D73A49;--shiki-dark:#F97583">+=</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> removeUnit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( minutes ) </span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 60000</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">	if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( seconds </span><span style="color:#D73A49;--shiki-dark:#F97583">!==</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		totalDelay </span><span style="color:#D73A49;--shiki-dark:#F97583">+=</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> removeUnit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( seconds ) </span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1000</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">	if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( miliseconds </span><span style="color:#D73A49;--shiki-dark:#F97583">!==</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		totalDelay </span><span style="color:#D73A49;--shiki-dark:#F97583">+=</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> removeUnit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( miliseconds );</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">	return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> totalDelay;</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"> removeUnit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">delay</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Hours</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Minutes</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Seconds</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> MiliSeconds</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> )</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	return</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Number</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( delay.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">replaceAll</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> /</span><span style="color:#005CC5;--shiki-dark:#79B8FF">[a-z]</span><span style="color:#032F62;--shiki-dark:#9ECBFF">/</span><span style="color:#D73A49;--shiki-dark:#F97583">g</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">''</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></code></pre>

			</div>
		</figure><h2 id="co-dalej"><a class="header-anchor" href="https://blog.comandeer.pl/projektujemy-czasomierze#co-dalej">Co dalej?</a></h2>
<p>Udało nam się zaprojektować nowe API dla czasomierzy. W następnym odcinku spróbujemy z tego skleić <em>profesjonalny</em> pakiet npm!</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, m.in. 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-67"><p class="note__label" id="note-67">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>
<div class="note" role="note" aria-labelledby="note-68"><p class="note__label" id="note-68">Dygresja</p><div class="note__content"><p>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></div></div>
<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>
	
</feed>
