<?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-04-25T11:06:04.890Z</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">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-64"><p class="note__label" id="note-64">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-65"><p class="note__label" id="note-65">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-66"><p class="note__label" id="note-66">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-67"><p class="note__label" id="note-67">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, <a href="http://m.in" rel="noreferrer noopener">m.in</a>. od tego, czy przeglądarka ma akurat “wolne”, czy procesor nie jest zajęty przez coś innego, czy okno przeglądarki jest sfocusowane, itd.</p>
<p>Zarówno <code>setTimeout()</code>, jak i <code>setInterval()</code> zwracają id, które może być następnie użyte w <code>clearTimeout()</code> i <code>clearInterval()</code>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> timeoutId</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> setTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( timeoutHandler, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">5000</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> );</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> intervalId</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> setInterval</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( intervalHandler, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">1000</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">clearTimeout</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( timeoutId );</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">clearInterval</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( intervalId );</span></span>
<span class="line"></span></code></pre>

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

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

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

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

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

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

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

			</div>
		</figure><p>Prawda, że ładniej?</p>
<h2 id="co-dalej"><a class="header-anchor" href="https://blog.comandeer.pl/tik-tak#co-dalej">Co dalej?</a></h2>
<p>Skoro znamy już problemy i bolączki natywnych czasomierzy w przeglądarkach, czas pomyśleć, w jaki sposób można by je rozwiązać. Ale to już temat na kolejny wpis!</p>
]]></content>
		</entry>
	
</feed>
