% js-async.tex — Promises (states, settle-once, sync executor, async reactions, chaining and
% adoption, errors, unhandled rejections), async/await desugared, waterfalls, the four
% combinators, forEach(async), AbortController, async iterators, retry + backoff, a pool of N,
% and a traced output-order example.
% Source: own knowledge, aligned with ECMAScript (PromiseReactionJob, PromiseResolveThenableJob,
% Await, HostPromiseRejectionTracker). Senior-interview level.
% Build ONLY with: tools/print/print-sheet.py <this>.tex --dry-run
% @source: hiot monorepo, docs/school/sheets/javascript/js-async.tex — the SOURCE OF TRUTH; a copy anywhere else (e.g. artur.gurgul.pro) is regenerated from it, never edited
% @labels: area=javascript kind=concept level=core platform=web new=no round=typescript-2026-09-24 topic=concurrency,language
% @tags: promises, async-await, microtask, macrotask, promise-all, promise-allsettled, promise-any, unhandled-rejection, abortcontroller, concurrency-pool, retry-backoff, async-iterators
\documentclass[8pt]{extarticle}
\usepackage{printup-sheet}
\usepackage{array}

\lstdefinelanguage{TSSheet}{
  morekeywords={import,from,export,default,const,let,var,type,interface,extends,function,
    return,async,await,if,else,switch,case,new,true,false,null,undefined,typeof,keyof,
    as,satisfies,never,string,number,boolean,void,declare,namespace,readonly,class,this,
    for,of,in,static,super,yield,delete,throw,try,catch,while,finally},
  sensitive=true, morecomment=[l]{//}, morecomment=[s]{/*}{*/},
  morestring=[b]", morestring=[b]', morestring=[b]`}

\tikzset{
  sb/.style={box, font=\scriptsize, inner sep=1.5pt, minimum height=4.2mm},
  lbl/.style={font=\tiny, text=black!75, inner sep=1pt, align=center},
  pt/.style={font=\bfseries\small, anchor=west},
  qc/.style={cell, font=\scriptsize\ttfamily, minimum width=4.5mm, minimum height=4.2mm},
}
\newcommand\fat{\hbox{=}\hbox{>}}% "=>" without the 0xProto ligature

\begin{document}

\sheettitle{Async JavaScript — promises, await, control}{javascript · memo}

\oneliner{A Promise is a \textbf{settle-once} box for a future value; its reactions always run
\textbf{later}, as \textbf{microtasks}, which the loop drains \emph{completely} after each task.
\texttt{async}/\texttt{await} is sugar over \texttt{then}: \texttt{await} \emph{always} suspends.
One thread, no preemption — concurrency, not parallelism; promises are \textbf{eager} and
\textbf{not cancellable} (that's \texttt{AbortSignal}'s job).}

\vspace{2pt}
\noindent\begin{tikzpicture}[sheet]
  \foreach \x in {4.6,10.3} \draw[sheetGrey!40] (\x,3.1) -- (\x,-0.45);
  % ── 1 state machine ──
  \node[pt] at (0,2.95) {\textcolor{sheetBlue}{1} states: settle once};
  \node[sb, minimum width=14mm] (pe) at (0.85,1.3) {pending};
  \node[sb, minimum width=14mm, draw=sheetGreen, fill=sheetGreen!10] (fu) at (3.3,2.2) {fulfilled(v)};
  \node[sb, minimum width=14mm, draw=sheetRed, fill=sheetRed!7] (rj) at (3.3,0.4) {rejected(e)};
  \draw[flow] (pe) -- node[lbl, above left]{\texttt{resolve(v)}} (fu);
  \draw[flow] (pe) -- node[lbl, below left]{\texttt{reject(e)} · throw} (rj);
  \node[lbl, text=sheetGrey] at (3.3,1.3) {settled = final;\\2nd resolve/reject\\is ignored};
  \node[lbl, anchor=west, align=left, text=sheetBrown] at (0,-0.25) {\texttt{resolve(thenable)}: stays pending, \emph{adopts} it};
  % ── 2 event loop ──
  \node[pt] at (4.7,2.95) {\textcolor{sheetBlue}{2} one loop turn};
  \node[sb, minimum width=17mm] (st) at (5.7,1.9) {call stack\\runs to empty};
  \node[sb, minimum width=19mm, draw=sheetOrange, fill=sheetOrange!8] (mi) at (8.6,1.9) {microtasks: drain \textbf{all}\\(incl. newly added)};
  \node[sb, minimum width=17mm, fill=black!4, draw=sheetGrey] (re) at (8.6,0.55) {render (browser)};
  \node[sb, minimum width=17mm] (ma) at (5.7,0.55) {take \textbf{one}\\macrotask};
  \draw[flow] (st) -- (mi); \draw[flow] (mi) -- (re); \draw[flow] (re) -- (ma); \draw[flow] (ma) -- (st);
  \node[lbl, anchor=west, align=left] at (4.7,2.45) {micro: \texttt{then}/\texttt{await} continuations, \texttt{queueMicrotask}};
  \node[lbl, anchor=west, align=left] at (4.7,-0.2) {macro: \texttt{setTimeout}, I/O, UI events · Node: \texttt{nextTick} before promises};
  % ── 3 the trace (example code in the left column) ──
  \node[pt] at (10.4,2.95) {\textcolor{sheetBlue}{3} the trace, after the sync phase};
  \node[lbl, anchor=west] at (10.4,2.45) {printed synchronously:};
  \foreach \v [count=\k] in {1,4,7,9} \node[qc, fill=sheetBlue!10] at (13.3+0.5*\k,2.45) {\v};
  \node[lbl, anchor=west] at (10.4,1.8) {microtask queue (FIFO):};
  \foreach \v/\w [count=\k] in {3/then,5/await,6/qMT,8/then} {
    \node[qc, draw=sheetOrange, fill=sheetOrange!10] at (13.3+0.5*\k,1.8) {\v};
    \node[lbl, font=\tiny] at (13.3+0.5*\k,1.45) {\w};}
  \node[lbl, anchor=west] at (10.4,0.95) {macrotask queue:};
  \node[qc, fill=black!6] at (13.8,0.95) {2};
  \node[lbl, anchor=west] at (14.15,0.95) {\texttt{setTimeout 0}};
  \node[sb, anchor=west, minimum width=58mm, draw=sheetGreen, fill=sheetGreen!10] at (10.4,0.2) {output: \texttt{1 4 7 9 3 5 6 8 2}};
  \node[lbl, anchor=west, text=sheetBrown] at (10.4,-0.3) {5 before 6: \texttt{await null} queued its continuation first};
\end{tikzpicture}

\begin{multicols}{2}

\section{Example — trace it (drawing 3)}
\begin{lstlisting}[language=TSSheet]
console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
(async () => { console.log(4); await null; console.log(5); })();
queueMicrotask(() => console.log(6));
new Promise(r => { console.log(7); r(); })  // executor is sync
  .then(() => console.log(8));
console.log(9);                         // 1 4 7 9 3 5 6 8 2
\end{lstlisting}

\section{How it works}
\begin{itemize}\raggedright
  \item The \textbf{executor runs synchronously} inside \texttt{new Promise}; a throw in it =
        rejection. \texttt{then} on an already-settled promise still queues a
        microtask — never synchronous (no Zalgo).
  \item \texttt{p.then(f)} returns a \textbf{new} promise: \texttt{f} returns \texttt{v}
        $\to$ fulfilled \texttt{v}; throws $\to$ rejected; returns a thenable $\to$
        \textbf{adopts} it (+2 ticks: a resolve-thenable job, then its \texttt{then}).
  \item Errors skip fulfilment handlers down the chain to the next \texttt{catch}
        (= \texttt{then(undefined, f)}). \texttt{finally} passes value/reason through.
        \texttt{then(ok, fail)}: \texttt{fail} does \emph{not} catch a throw in \texttt{ok}.
  \item \textbf{Unhandled rejection}: rejected with no handler once microtasks drain $\to$
        browser \texttt{unhandledrejection} event; Node $\geq$ 15 crashes the process by default.
  \item \textbf{async fn}: runs synchronously up to its first \texttt{await}, always returns a
        promise (\texttt{return v} fulfils, \texttt{throw} rejects).
        \texttt{await x} $\approx$ \texttt{Promise.resolve(x).then(resume, throwInto)} — even for
        \texttt{await 1}: code after an \texttt{await} is always a later microtask.
  \item Inside \texttt{try}, write \texttt{return await p}: plain \texttt{return p} leaves the
        function before \texttt{p} rejects, so the \texttt{catch} never sees it.
  \item \textbf{Swift:} a throwing \texttt{TaskGroup} child cancels its siblings;
        \texttt{Promise.all} does \emph{not}. One JS thread: no data races, but state can
        change at every \texttt{await} (like actor reentrancy).
\end{itemize}

\section{Combinators}
{\scriptsize\setlength{\tabcolsep}{2.5pt}
\begin{tabular}{@{}>{\raggedright}p{17mm}>{\raggedright}p{25mm}>{\raggedright\arraybackslash}p{29mm}@{}}
\toprule
 & fulfils with & rejects with \\ \midrule
\texttt{all} & all values, \emph{input} order & the \textbf{first} rejection (fail-fast) \\
\texttt{allSettled} & \texttt{\{status, value|reason\}[]} & never \\
\texttt{race} & first to settle, either way & first to settle, either way \\
\texttt{any} & first \emph{fulfilment} & \texttt{AggregateError} (\texttt{.errors}) if all reject \\ \bottomrule
\end{tabular}}\\[1pt]
{\scriptsize Empty: \texttt{all} $\to$ \texttt{[]} · \texttt{any} rejects · \texttt{race}
never settles. \texttt{allSettled} ES2020, \texttt{any} ES2021.}

\section{Waterfall, forEach, pool}
\begin{lstlisting}[language=TSSheet]
const a = await getA(); const b = await getB();   // tA + tB
const [a, b] = await Promise.all([getA(), getB()]); // max
items.forEach(async x => await save(x)); // NOT awaited!
for (const x of items) await save(x);             // sequential
await Promise.all(items.map(x => save(x)));       // all at once
\end{lstlisting}
\noindent\begin{minipage}{\linewidth}
\begin{lstlisting}[language=TSSheet]
async function pool(items, n, work) {             // at most n
  const out = []; let next = 0;
  const lane = async () => { while (next < items.length) {
    const i = next++; out[i] = await work(items[i]); } };
  await Promise.all(Array.from({ length: n }, lane));
  return out; }        // next++ is safe: nothing preempts it
\end{lstlisting}
\end{minipage}

\section{Cancel, retry, stream}
\begin{lstlisting}[language=TSSheet]
const ac = new AbortController();
const signal = AbortSignal.any([ac.signal,
                                AbortSignal.timeout(5000)]);
await fetch(url, { signal });  // ac.abort() -> rejects
async function retry(fn, tries = 5, base = 200) {
  for (let i = 0; ; i++) {
    try { return await fn(); }
    catch (e) { if (i + 1 >= tries) throw e;
      const ms = base * 2 ** i * (0.5 + Math.random()); // + jitter
      await new Promise(r => setTimeout(r, ms)); } } }
async function* pages(url) {      // Symbol.asyncIterator
  while (url) { const p = await (await fetch(url)).json();
    yield* p.items; url = p.next; } }
for await (const item of pages('/feed')) render(item);
\end{lstlisting}
\begin{itemize}\raggedright
  \item Abort only \emph{signals}: \texttt{fetch} listens (\texttt{AbortError} /
        \texttt{TimeoutError}); your code must check \texttt{signal.throwIfAborted()}.
  \item Retry only idempotent calls on retryable errors (network, 5xx, 429).
        \texttt{break} in \texttt{for await} calls the generator's \texttt{return()}.
\end{itemize}

\section{Interview traps}
\begin{itemize}\raggedright
  \trap{\texttt{pa = getA(); pb = getB(); await pa; await pb} is parallel, but a \texttt{pb}
        rejecting during \texttt{await pa} is \emph{unhandled} — use \texttt{Promise.all}.}
  \trap{\texttt{Promise.all} rejecting does not stop the others; and a \texttt{setTimeout(0)}
        never beats an already-queued microtask.}
\end{itemize}

\section{Remember}
\emph{Executor now, reactions later · drain all micro, then one macro · await always yields ·
abort is a signal, not a kill.}

\section{Likely questions}
\begin{enumerate}\raggedright
  \item Micro vs macro? — all micro drain after each task, before render.
  \item Cancel a promise? — you can't; cancel the \emph{work} (\texttt{AbortSignal}).
  \item Limit concurrency? — N lanes on a shared index (above), or p-limit.
\end{enumerate}

\end{multicols}

\noindent{\footnotesize\color{sheetGrey}\textit{Related:} js-scope-closures-this · js-prototypes-objects-classes
(generators) · js-types-coercion-modules (top-level await) · Swift \texttt{async let} / \texttt{TaskGroup}}

\end{document}
