<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>지나가는 21세기 개발자의 이야기</title>
    <link>https://khys.tistory.com/</link>
    <description>아는 게 힘이다.</description>
    <language>ko</language>
    <pubDate>Fri, 26 Jun 2026 00:47:38 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>토리나나</managingEditor>
    <image>
      <title>지나가는 21세기 개발자의 이야기</title>
      <url>https://tistory1.daumcdn.net/tistory/5116555/attach/a159491720fb4fd9a73612568779280f</url>
      <link>https://khys.tistory.com</link>
    </image>
    <item>
      <title>실무 Promise 아키텍처: 취소, 타임아웃, 재시도, 메모리와 작업 수명</title>
      <link>https://khys.tistory.com/115</link>
      <description>&lt;blockquote data-end=&quot;34510&quot; data-start=&quot;34476&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;34510&quot; data-start=&quot;34478&quot; data-ke-size=&quot;size16&quot;&gt;Promise와 async/await 깊이 이해하기 4/4&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-end=&quot;34519&quot; data-start=&quot;34512&quot; data-section-id=&quot;1fudykl&quot; data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-end=&quot;34548&quot; data-start=&quot;34521&quot; data-ke-size=&quot;size16&quot;&gt;Promise 코드를 작성하는 것은 어렵지 않다.&lt;/p&gt;
&lt;p data-end=&quot;34576&quot; data-start=&quot;34550&quot; data-ke-size=&quot;size16&quot;&gt;어려운 것은 작업의 생명주기를 설계하는 일이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;사용자가 페이지를 떠나면 요청을 중단해야 하는가?
timeout이 발생하면 실제 네트워크 요청도 종료되는가?
부모 작업이 실패하면 자식 작업은 어떻게 되는가?
어떤 오류를 재시도해도 안전한가?
영원히 pending인 Promise는 무엇을 메모리에 유지하는가?
하나가 아닌 여러 비동기 값은 무엇으로 표현해야 하는가?&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;34810&quot; data-start=&quot;34768&quot; data-ke-size=&quot;size16&quot;&gt;Promise는 성공과 실패를 표현하지만 다음 요소를 직접 제공하지 않는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;취소
timeout
자원 소유권
재시도 정책
멱등성
backpressure
구조적 동시성&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;34906&quot; data-start=&quot;34874&quot; data-ke-size=&quot;size16&quot;&gt;이 요소들은 애플리케이션 아키텍처가 별도로 설계해야 한다.&lt;/p&gt;
&lt;hr data-end=&quot;34911&quot; data-start=&quot;34908&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;34935&quot; data-start=&quot;34913&quot; data-section-id=&quot;1ftx1fr&quot; data-ke-size=&quot;size26&quot;&gt;1. Promise에는 취소가 없다&lt;/h2&gt;
&lt;p data-end=&quot;34970&quot; data-start=&quot;34937&quot; data-ke-size=&quot;size16&quot;&gt;Promise는 작업 자체가 아니라 결과를 관찰하는 객체다.&lt;/p&gt;
&lt;p data-end=&quot;34991&quot; data-start=&quot;34972&quot; data-ke-size=&quot;size16&quot;&gt;이 차이는 취소 설계에서 중요하다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;const sharedRequest = fetch(&quot;/api/config&quot;);

const consumerA = sharedRequest.then(useForA);
const consumerB = sharedRequest.then(useForB);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;35172&quot; data-start=&quot;35143&quot; data-ke-size=&quot;size16&quot;&gt;하나의 요청 결과를 두 소비자가 함께 사용하고 있다.&lt;/p&gt;
&lt;p data-end=&quot;35246&quot; data-start=&quot;35174&quot; data-ke-size=&quot;size16&quot;&gt;consumerA가 더 이상 결과를 원하지 않는다고 해서 실제 네트워크 요청을 중단하면 consumerB도 영향을 받는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;결과 관찰을 중단한다
&amp;ne;

실제 작업을 중단한다&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;35342&quot; data-start=&quot;35287&quot; data-ke-size=&quot;size16&quot;&gt;Promise 객체 자체에 보편적인 cancel()이 없는 이유를 이 관점에서 이해할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;35389&quot; data-start=&quot;35344&quot; data-ke-size=&quot;size16&quot;&gt;취소 권한은 Promise보다 &lt;b&gt;작업을 생성하고 소유한 계층&lt;/b&gt;에 있어야 한다.&lt;/p&gt;
&lt;hr data-end=&quot;35394&quot; data-start=&quot;35391&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;35430&quot; data-start=&quot;35396&quot; data-section-id=&quot;d35lvf&quot; data-ke-size=&quot;size26&quot;&gt;2. AbortController와 AbortSignal&lt;/h2&gt;
&lt;p data-end=&quot;35499&quot; data-start=&quot;35432&quot; data-ke-size=&quot;size16&quot;&gt;웹 플랫폼에서는 취소 의사를 전달하는 표준 구조로 AbortController와 AbortSignal을 사용한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;const controller = new AbortController();

const request = fetch(&quot;/api/users&quot;, {
  signal: controller.signal,
});

controller.abort();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;35660&quot; data-start=&quot;35647&quot; data-ke-size=&quot;size16&quot;&gt;각 역할은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;AbortController
= 취소를 발생시키는 주체

AbortSignal
= 취소 상태와 이유를 전달하는 객체&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;35932&quot; data-start=&quot;35739&quot; data-ke-size=&quot;size16&quot;&gt;Fetch는 요청에 연결된 AbortSignal을 통해 취소 상태를 관찰한다. DOM Standard는 signal의 취소 상태와 signal 결합&amp;middot;timeout 관련 연산을 정의하고, Fetch Standard는 Request가 AbortSignal을 갖도록 정의한다.&lt;/p&gt;
&lt;p data-end=&quot;35950&quot; data-start=&quot;35934&quot; data-ke-size=&quot;size16&quot;&gt;취소는 기본적으로 협력적이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;controller.abort()
&amp;rarr; 취소 의사를 전달

실제 작업
&amp;rarr; signal을 관찰하고 정지해야 함&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;36070&quot; data-start=&quot;36024&quot; data-ke-size=&quot;size16&quot;&gt;아무 연산이나 AbortSignal을 전달한다고 자동으로 중단되는 것은 아니다.&lt;/p&gt;
&lt;p data-end=&quot;36096&quot; data-start=&quot;36072&quot; data-ke-size=&quot;size16&quot;&gt;해당 API가 signal을 지원해야 한다.&lt;/p&gt;
&lt;hr data-end=&quot;36101&quot; data-start=&quot;36098&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;36126&quot; data-start=&quot;36103&quot; data-section-id=&quot;rh4a8q&quot; data-ke-size=&quot;size26&quot;&gt;3. timeout과 취소 신호 결합&lt;/h2&gt;
&lt;p data-end=&quot;36187&quot; data-start=&quot;36128&quot; data-ke-size=&quot;size16&quot;&gt;DOM Standard에는 timeout signal과 여러 signal을 결합하는 구조가 정의되어 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const timeoutSignal = AbortSignal.timeout(3000);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;36274&quot; data-start=&quot;36249&quot; data-ke-size=&quot;size16&quot;&gt;외부 취소와 timeout을 결합할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;function createRequestSignal({
  signal,
  timeoutMs,
}) {
  const signals = [
    AbortSignal.timeout(timeoutMs),
  ];

  if (signal) {
    signals.push(signal);
  }

  return AbortSignal.any(signals);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;36506&quot; data-start=&quot;36492&quot; data-ke-size=&quot;size16&quot;&gt;사용 예시는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;async function fetchJson(
  url,
  {
    signal,
    timeoutMs = 3000,
  } = {},
) {
  const requestSignal = createRequestSignal({
    signal,
    timeoutMs,
  });

  const response = await fetch(url, {
    signal: requestSignal,
  });

  if (!response.ok) {
    throw new Error(
      `HTTP ${response.status}: ${url}`,
    );
  }

  return response.json();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;36911&quot; data-start=&quot;36880&quot; data-ke-size=&quot;size16&quot;&gt;이제 작업은 두 경우 중 먼저 발생하는 이유로 취소된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;외부 signal 취소
또는
timeout 만료&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;37036&quot; data-start=&quot;36952&quot; data-ke-size=&quot;size16&quot;&gt;배포 환경에서 해당 정적 메서드를 지원하지 않는다면 AbortController와 setTimeout()으로 같은 정책을 직접 구현할 수 있다.&lt;/p&gt;
&lt;hr data-end=&quot;37041&quot; data-start=&quot;37038&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;37073&quot; data-start=&quot;37043&quot; data-section-id=&quot;zi1y80&quot; data-ke-size=&quot;size26&quot;&gt;4. Promise.race timeout의 한계&lt;/h2&gt;
&lt;p data-end=&quot;37119&quot; data-start=&quot;37075&quot; data-ke-size=&quot;size16&quot;&gt;다음 timeout 구현은 결과 대기를 중단하지만 실제 작업은 중단하지 않는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function timeout(ms) {
  return new Promise((_, reject) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      reject(new Error(&quot;Timeout&quot;));
    }, ms);
  });
}

await Promise.race([
  fetch(&quot;/api/data&quot;),
  timeout(3000),
]);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;37385&quot; data-start=&quot;37337&quot; data-ke-size=&quot;size16&quot;&gt;3초 뒤 timeout Promise가 먼저 reject되면 호출자는 실패를 관찰한다.&lt;/p&gt;
&lt;p data-end=&quot;37420&quot; data-start=&quot;37387&quot; data-ke-size=&quot;size16&quot;&gt;그러나 원래 fetch() 요청은 계속 진행될 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Promise.race에서 패배
&amp;ne; 작업 취소&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;37496&quot; data-start=&quot;37460&quot; data-ke-size=&quot;size16&quot;&gt;실제 요청을 중단하려면 요청 자체에 signal을 전달해야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;const response = await fetch(&quot;/api/data&quot;, {
  signal: AbortSignal.timeout(3000),
});&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;37641&quot; data-start=&quot;37594&quot; data-ke-size=&quot;size16&quot;&gt;Promise 조합자는 결과 선택 정책을 정의할 뿐 작업 생명주기를 관리하지 않는다.&lt;/p&gt;
&lt;hr data-end=&quot;37646&quot; data-start=&quot;37643&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;37681&quot; data-start=&quot;37648&quot; data-section-id=&quot;kt3ozs&quot; data-ke-size=&quot;size26&quot;&gt;5. 취소, timeout, 실패는 같은 의미가 아니다&lt;/h2&gt;
&lt;p data-end=&quot;37730&quot; data-start=&quot;37683&quot; data-ke-size=&quot;size16&quot;&gt;Promise API에서는 이 세 상황이 모두 rejection으로 나타날 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;네트워크 실패
사용자 취소
timeout&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;37783&quot; data-start=&quot;37767&quot; data-ke-size=&quot;size16&quot;&gt;그러나 도메인 의미는 다르다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;실패
= 완료하려 했으나 성공하지 못함

취소
= 더 이상 완료를 원하지 않음

timeout
= 정해진 시간 안에 완료되지 않음&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;37906&quot; data-start=&quot;37870&quot; data-ke-size=&quot;size16&quot;&gt;이를 모두 일반 Error로 처리하면 다음 문제가 생길 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;사용자 취소가 오류 모니터링에 보고됨
timeout과 서버 오류를 구분할 수 없음
취소된 요청이 자동 재시도됨
잘못된 사용자 안내 메시지가 표시됨&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;38019&quot; data-start=&quot;38002&quot; data-ke-size=&quot;size16&quot;&gt;예외 클래스를 분리할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;class TimeoutError extends Error {
  constructor(message = &quot;작업 시간이 초과되었습니다.&quot;) {
    super(message);
    this.name = &quot;TimeoutError&quot;;
  }
}

class UserCanceledError extends Error {
  constructor(message = &quot;사용자가 작업을 취소했습니다.&quot;) {
    super(message);
    this.name = &quot;UserCanceledError&quot;;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;38358&quot; data-start=&quot;38320&quot; data-ke-size=&quot;size16&quot;&gt;또는 예상 가능한 결과를 tagged union으로 표현할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;type OperationResult&amp;lt;T&amp;gt; =
  | {
      type: &quot;success&quot;;
      value: T;
    }
  | {
      type: &quot;canceled&quot;;
      reason: unknown;
    }
  | {
      type: &quot;timeout&quot;;
      duration: number;
    }
  | {
      type: &quot;failure&quot;;
      error: Error;
    };&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;38652&quot; data-start=&quot;38622&quot; data-ke-size=&quot;size16&quot;&gt;Promise rejection은 제어 채널일 뿐이다.&lt;/p&gt;
&lt;p data-end=&quot;38673&quot; data-start=&quot;38654&quot; data-ke-size=&quot;size16&quot;&gt;도메인 오류 분류는 별도의 설계다.&lt;/p&gt;
&lt;hr data-end=&quot;38678&quot; data-start=&quot;38675&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;38710&quot; data-start=&quot;38680&quot; data-section-id=&quot;o2jow6&quot; data-ke-size=&quot;size26&quot;&gt;6. Floating Promise와 작업 소유권&lt;/h2&gt;
&lt;p data-end=&quot;38740&quot; data-start=&quot;38712&quot; data-ke-size=&quot;size16&quot;&gt;다음 함수는 두 작업을 시작하지만 기다리지 않는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async function updateUser() {
  saveAuditLog();
  sendNotification();

  return updateDatabase();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;38914&quot; data-start=&quot;38853&quot; data-ke-size=&quot;size16&quot;&gt;saveAuditLog()와 sendNotification()이 Promise를 반환한다고 가정해보자.&lt;/p&gt;
&lt;p data-end=&quot;38951&quot; data-start=&quot;38916&quot; data-ke-size=&quot;size16&quot;&gt;이 Promise들은 부모 함수의 완료 조건에 포함되지 않는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;updateUser 완료
├─ saveAuditLog는 계속 실행될 수 있음
└─ sendNotification도 계속 실행될 수 있음&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;39079&quot; data-start=&quot;39041&quot; data-ke-size=&quot;size16&quot;&gt;이런 Promise를 흔히 floating Promise라고 부른다.&lt;/p&gt;
&lt;p data-end=&quot;39106&quot; data-start=&quot;39081&quot; data-ke-size=&quot;size16&quot;&gt;문제는 작업의 소유자가 불분명해진다는 것이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;오류는 누가 처리하는가?
요청이 끝난 뒤에도 실행되어도 되는가?
부모가 취소되면 자식은 어떻게 되는가?
테스트는 언제 완료되었다고 판단하는가?&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;39231&quot; data-start=&quot;39200&quot; data-ke-size=&quot;size16&quot;&gt;부모가 자식 작업을 소유한다면 명시적으로 기다려야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;async function updateUser({
  signal,
}) {
  await Promise.all([
    saveAuditLog({ signal }),
    sendNotification({ signal }),
    updateDatabase({ signal }),
  ]);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;39458&quot; data-start=&quot;39413&quot; data-ke-size=&quot;size16&quot;&gt;의도적인 fire-and-forget이라면 그 사실과 오류 정책을 드러내야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;moonscript&quot;&gt;&lt;code&gt;void sendTelemetry()
  .catch((error) =&amp;gt; {
    reportTelemetryError(error);
  });&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;39565&quot; data-start=&quot;39553&quot; data-ke-size=&quot;size16&quot;&gt;다음 코드보다는 낫다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;sendTelemetry();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;39623&quot; data-start=&quot;39595&quot; data-ke-size=&quot;size16&quot;&gt;void가 작업을 안전하게 만드는 것은 아니다.&lt;/p&gt;
&lt;p data-end=&quot;39663&quot; data-start=&quot;39625&quot; data-ke-size=&quot;size16&quot;&gt;다만 결과를 기다리지 않는 선택이 의도적이라는 것을 코드에 표시한다.&lt;/p&gt;
&lt;hr data-end=&quot;39668&quot; data-start=&quot;39665&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;39689&quot; data-start=&quot;39670&quot; data-section-id=&quot;3fef4p&quot; data-ke-size=&quot;size26&quot;&gt;7. 구조적 동시성이라는 관점&lt;/h2&gt;
&lt;p data-end=&quot;39743&quot; data-start=&quot;39691&quot; data-ke-size=&quot;size16&quot;&gt;구조적 동시성의 핵심 아이디어는 비동기 작업의 생명주기를 코드 블록의 생명주기에 묶는 것이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;부모 scope가 끝나기 전에
&amp;rarr; 자식 작업을 기다린다.

부모가 취소되면
&amp;rarr; 자식에게 취소를 전달한다.

자식이 실패하면
&amp;rarr; 형제 작업 처리 정책을 명시한다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;39889&quot; data-start=&quot;39847&quot; data-ke-size=&quot;size16&quot;&gt;JavaScript Promise는 이러한 규칙을 자동으로 강제하지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;39919&quot; data-start=&quot;39891&quot; data-ke-size=&quot;size16&quot;&gt;개발자가 다음 수단으로 직접 구조를 만들어야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;await
Promise.all
AbortSignal 전달
try/finally
자원 cleanup
명시적인 오류 경계&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;40029&quot; data-start=&quot;40000&quot; data-ke-size=&quot;size16&quot;&gt;다음 함수는 작업 수명이 함수 블록 안에 묶여 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;async function loadPage({
  signal,
}) {
  const userPromise = loadUser({
    signal,
  });

  const settingsPromise = loadSettings({
    signal,
  });

  const [user, settings] = await Promise.all([
    userPromise,
    settingsPromise,
  ]);

  return {
    user,
    settings,
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;40381&quot; data-start=&quot;40329&quot; data-ke-size=&quot;size16&quot;&gt;loadPage()의 Promise가 완료될 때는 자신이 소유한 두 작업도 완료된 상태다.&lt;/p&gt;
&lt;hr data-end=&quot;40386&quot; data-start=&quot;40383&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;40418&quot; data-start=&quot;40388&quot; data-section-id=&quot;jbmgpp&quot; data-ke-size=&quot;size26&quot;&gt;8. 재시도 대상은 Promise가 아니라 함수다&lt;/h2&gt;
&lt;p data-end=&quot;40444&quot; data-start=&quot;40420&quot; data-ke-size=&quot;size16&quot;&gt;다음 구조로는 작업을 다시 실행할 수 없다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;const promise = fetch(&quot;/api/data&quot;);

await retry(promise);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;40552&quot; data-start=&quot;40516&quot; data-ke-size=&quot;size16&quot;&gt;이미 생성된 Promise를 다시 기다려도 같은 결과만 관찰한다.&lt;/p&gt;
&lt;p data-end=&quot;40594&quot; data-start=&quot;40554&quot; data-ke-size=&quot;size16&quot;&gt;재시도 함수는 새로운 Promise를 만들 수 있는 함수를 받아야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;await retry(() =&amp;gt; {
  return fetch(&quot;/api/data&quot;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;40689&quot; data-start=&quot;40660&quot; data-ke-size=&quot;size16&quot;&gt;signal을 지원하는 지연 함수를 먼저 작성해보자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;function delay(
  ms,
  {
    signal,
  } = {},
) {
  return new Promise((resolve, reject) =&amp;gt; {
    if (signal?.aborted) {
      reject(signal.reason);
      return;
    }

    const timerId = setTimeout(() =&amp;gt; {
      cleanup();
      resolve();
    }, ms);

    function handleAbort() {
      clearTimeout(timerId);
      cleanup();
      reject(signal.reason);
    }

    function cleanup() {
      signal?.removeEventListener(
        &quot;abort&quot;,
        handleAbort,
      );
    }

    signal?.addEventListener(
      &quot;abort&quot;,
      handleAbort,
      {
        once: true,
      },
    );
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;41324&quot; data-start=&quot;41302&quot; data-ke-size=&quot;size16&quot;&gt;재시도 함수는 다음처럼 작성할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;async function retry(
  operation,
  {
    attempts = 3,
    baseDelayMs = 200,
    signal,
    shouldRetry = () =&amp;gt; true,
  } = {},
) {
  let lastError;

  for (
    let attempt = 1;
    attempt &amp;lt;= attempts;
    attempt += 1
  ) {
    signal?.throwIfAborted();

    try {
      return await operation({
        attempt,
        signal,
      });
    } catch (error) {
      lastError = error;

      const isLastAttempt =
        attempt === attempts;

      if (
        isLastAttempt ||
        !shouldRetry(error, attempt)
      ) {
        throw error;
      }

      const exponentialDelay =
        baseDelayMs * 2 ** (attempt - 1);

      const jitter =
        Math.random() * baseDelayMs;

      await delay(
        exponentialDelay + jitter,
        {
          signal,
        },
      );
    }
  }

  throw lastError;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;42184&quot; data-start=&quot;42170&quot; data-ke-size=&quot;size16&quot;&gt;사용 예시는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;const response = await retry(
  ({ signal }) =&amp;gt; {
    return fetch(&quot;/api/data&quot;, {
      signal,
    });
  },
  {
    attempts: 3,
    signal,
    shouldRetry(error) {
      return error instanceof TypeError;
    },
  },
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;42423&quot; data-start=&quot;42420&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;42439&quot; data-start=&quot;42425&quot; data-section-id=&quot;12vr3r&quot; data-ke-size=&quot;size26&quot;&gt;9. 재시도와 멱등성&lt;/h2&gt;
&lt;p data-end=&quot;42472&quot; data-start=&quot;42441&quot; data-ke-size=&quot;size16&quot;&gt;기술적으로 재시도할 수 있다고 해서 안전한 것은 아니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;GET 요청
&amp;rarr; 일반적으로 재시도하기 쉬움

결제 승인
&amp;rarr; 중복 결제 가능

이메일 전송
&amp;rarr; 중복 발송 가능

주문 생성
&amp;rarr; 중복 주문 가능&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;42605&quot; data-start=&quot;42565&quot; data-ke-size=&quot;size16&quot;&gt;재시도 안전성은 Promise가 아니라 작업의 도메인 의미에 달려 있다.&lt;/p&gt;
&lt;p data-end=&quot;42642&quot; data-start=&quot;42607&quot; data-ke-size=&quot;size16&quot;&gt;부수 효과가 있는 요청에는 다음과 같은 설계가 필요할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;멱등성 키
중복 요청 식별자
서버 측 deduplication
트랜잭션
처리 상태 조회
보상 작업&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;42744&quot; data-start=&quot;42711&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 결제 요청에 고유한 멱등성 키를 전달할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;await retry(
  ({ signal }) =&amp;gt; {
    return fetch(&quot;/api/payments&quot;, {
      method: &quot;POST&quot;,
      headers: {
        &quot;Idempotency-Key&quot;: paymentRequestId,
      },
      body: JSON.stringify(payment),
      signal,
    });
  },
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;43014&quot; data-start=&quot;42986&quot; data-ke-size=&quot;size16&quot;&gt;재시도 정책은 오류 종류에 따라서도 달라져야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;일시적 네트워크 실패
&amp;rarr; 재시도 가능

인증 실패
&amp;rarr; 자동 재시도 의미 없음

유효성 검사 실패
&amp;rarr; 같은 입력으로 재시도해도 실패

서버 rate limit
&amp;rarr; Retry-After 정책 고려&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;43139&quot; data-start=&quot;43136&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;43165&quot; data-start=&quot;43141&quot; data-section-id=&quot;1s3jiff&quot; data-ke-size=&quot;size26&quot;&gt;10. 예상 가능한 실패와 예외적 실패&lt;/h2&gt;
&lt;p data-end=&quot;43201&quot; data-start=&quot;43167&quot; data-ke-size=&quot;size16&quot;&gt;모든 비정상 결과를 rejection으로 표현할 필요는 없다.&lt;/p&gt;
&lt;p data-end=&quot;43227&quot; data-start=&quot;43203&quot; data-ke-size=&quot;size16&quot;&gt;사용자 검색 결과가 없는 상황을 생각해보자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;검색 결과 없음&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;43284&quot; data-start=&quot;43250&quot; data-ke-size=&quot;size16&quot;&gt;이것은 시스템 오류가 아니라 정상적인 도메인 결과일 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;type SearchResult&amp;lt;T&amp;gt; =
  | {
      type: &quot;found&quot;;
      value: T;
    }
  | {
      type: &quot;not-found&quot;;
    };&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;43426&quot; data-start=&quot;43407&quot; data-ke-size=&quot;size16&quot;&gt;구현은 다음과 같이 만들 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;async function searchUser(
  userId,
  {
    signal,
  },
) {
  const response = await fetch(
    `/api/users/${userId}`,
    {
      signal,
    },
  );

  if (response.status === 404) {
    return {
      type: &quot;not-found&quot;,
    };
  }

  if (!response.ok) {
    throw new Error(
      `HTTP ${response.status}`,
    );
  }

  return {
    type: &quot;found&quot;,
    value: await response.json(),
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;43857&quot; data-start=&quot;43836&quot; data-ke-size=&quot;size16&quot;&gt;제어 흐름을 다음처럼 분리할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;예상 가능한 도메인 분기
&amp;rarr; fulfillment 안의 tagged result

예외적 인프라 실패
&amp;rarr; rejection&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;43994&quot; data-start=&quot;43940&quot; data-ke-size=&quot;size16&quot;&gt;모든 상황을 rejection으로 보내면 정상적인 분기까지 try/catch에 의존하게 된다.&lt;/p&gt;
&lt;hr data-end=&quot;43999&quot; data-start=&quot;43996&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;44031&quot; data-start=&quot;44001&quot; data-section-id=&quot;1fc652z&quot; data-ke-size=&quot;size26&quot;&gt;11. pending Promise와 메모리 유지&lt;/h2&gt;
&lt;p data-end=&quot;44057&quot; data-start=&quot;44033&quot; data-ke-size=&quot;size16&quot;&gt;Promise는 후속 반응 목록을 유지한다.&lt;/p&gt;
&lt;p data-end=&quot;44096&quot; data-start=&quot;44059&quot; data-ke-size=&quot;size16&quot;&gt;각 반응 handler는 클로저이므로 외부 변수를 참조할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const never = new Promise(() =&amp;gt; {});

function registerHandler() {
  const largeData = new Array(
    1_000_000,
  ).fill(&quot;data&quot;);

  never.then(() =&amp;gt; {
    console.log(largeData.length);
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;44353&quot; data-start=&quot;44305&quot; data-ke-size=&quot;size16&quot;&gt;never가 영원히 settle되지 않는다면 등록된 handler도 계속 필요하다.&lt;/p&gt;
&lt;p data-end=&quot;44410&quot; data-start=&quot;44355&quot; data-ke-size=&quot;size16&quot;&gt;handler가 largeData를 참조하므로 해당 데이터도 도달 가능한 상태로 남을 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;pending Promise
  └─ fulfillment reaction
       └─ handler closure
            └─ largeData&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;44546&quot; data-start=&quot;44517&quot; data-ke-size=&quot;size16&quot;&gt;이를 반복하면 반응과 데이터가 계속 누적될 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;setInterval(registerHandler, 1000);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;44752&quot; data-start=&quot;44595&quot; data-ke-size=&quot;size16&quot;&gt;Promise가 settle되면 명세 알고리즘은 저장된 반응 목록을 꺼내 Job을 생성하고 Promise 내부 목록을 정리한다. 반대로 영원히 pending인 Promise는 반응 목록을 계속 유지할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;44778&quot; data-start=&quot;44754&quot; data-ke-size=&quot;size16&quot;&gt;Promise 자체가 메모리 누수는 아니다.&lt;/p&gt;
&lt;p data-end=&quot;44792&quot; data-start=&quot;44780&quot; data-ke-size=&quot;size16&quot;&gt;문제는 다음 조합이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;장기간 pending인 Promise
+
계속 추가되는 handler
+
handler가 참조하는 큰 객체&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;44869&quot; data-start=&quot;44866&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;44894&quot; data-start=&quot;44871&quot; data-section-id=&quot;10b2fge&quot; data-ke-size=&quot;size26&quot;&gt;12. 중단된 async 함수의 상태&lt;/h2&gt;
&lt;p data-end=&quot;44928&quot; data-start=&quot;44896&quot; data-ke-size=&quot;size16&quot;&gt;다음 async 함수는 await에서 영원히 중단된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async function holdLargeData() {
  const largeData = new Array(
    1_000_000,
  ).fill(&quot;data&quot;);

  await neverSettlingPromise;

  return largeData.length;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;45129&quot; data-start=&quot;45099&quot; data-ke-size=&quot;size16&quot;&gt;await 이후에 largeData가 필요하다.&lt;/p&gt;
&lt;p data-end=&quot;45166&quot; data-start=&quot;45131&quot; data-ke-size=&quot;size16&quot;&gt;따라서 중단된 실행은 재개를 위해 필요한 상태를 보존해야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;중단 지점
지역 변수
렉시컬 환경
외부 Promise 결과
재개 함수&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;45349&quot; data-start=&quot;45219&quot; data-ke-size=&quot;size16&quot;&gt;ECMAScript의 Await 과정도 현재 async 실행 컨텍스트를 캡처한 반응을 생성하고, Promise가 완료되면 해당 컨텍스트를 재개하도록 정의한다.&lt;/p&gt;
&lt;p data-end=&quot;45413&quot; data-start=&quot;45351&quot; data-ke-size=&quot;size16&quot;&gt;엔진이 불필요한 값을 최적화할 수는 있지만, 향후 실행에서 필요할 수 있는 상태는 의미론적으로 유지되어야 한다.&lt;/p&gt;
&lt;p data-end=&quot;45434&quot; data-start=&quot;45415&quot; data-ke-size=&quot;size16&quot;&gt;따라서 다음 가정은 안전하지 않다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;현재 실행 중이 아닌 async 함수는
메모리를 거의 사용하지 않을 것이다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;45494&quot; data-start=&quot;45491&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;45530&quot; data-start=&quot;45496&quot; data-section-id=&quot;1wf8axn&quot; data-ke-size=&quot;size26&quot;&gt;13. Promise는 여러 값의 흐름을 표현하지 못한다&lt;/h2&gt;
&lt;p data-end=&quot;45559&quot; data-start=&quot;45532&quot; data-ke-size=&quot;size16&quot;&gt;Promise는 단 하나의 최종 결과를 표현한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;0회 또는 1회의 settlement&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;45636&quot; data-start=&quot;45594&quot; data-ke-size=&quot;size16&quot;&gt;다음과 같이 시간에 따라 계속 값이 발생하는 시스템에는 맞지 않을 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;WebSocket 메시지
마우스 이벤트
스트리밍 응답
파일 청크
실시간 가격 데이터&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;45727&quot; data-start=&quot;45697&quot; data-ke-size=&quot;size16&quot;&gt;단일 메시지를 Promise로 기다리는 것은 가능하다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function nextMessage(socket) {
  return new Promise((resolve) =&amp;gt; {
    socket.addEventListener(
      &quot;message&quot;,
      resolve,
      {
        once: true,
      },
    );
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;45950&quot; data-start=&quot;45920&quot; data-ke-size=&quot;size16&quot;&gt;하지만 지속적인 메시지 흐름에는 다음 문제들이 생긴다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;생산자가 소비자보다 빠르면 어떻게 하는가?
버퍼는 얼마나 유지하는가?
소비자가 중단하면 listener는 어떻게 정리하는가?
일부 메시지를 버릴 수 있는가?&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;46079&quot; data-start=&quot;46052&quot; data-ke-size=&quot;size16&quot;&gt;이런 문제에는 다음 추상화가 더 적합할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;golo&quot;&gt;&lt;code&gt;AsyncIterator
ReadableStream
Observable
이벤트 채널
메시지 큐&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;46288&quot; data-start=&quot;46146&quot; data-ke-size=&quot;size16&quot;&gt;Async Iterator는 next() 호출이 다음 IteratorResult를 담은 Promise를 반환하는 프로토콜이며, for await...of로 소비할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;for await (const chunk of stream) {
  await processChunk(chunk);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;46428&quot; data-start=&quot;46368&quot; data-ke-size=&quot;size16&quot;&gt;이 구조에서는 다음 값을 요청하는 시점과 처리 완료 시점을 연결해 backpressure를 표현할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;Promise
= 하나의 최종 결과

AsyncIterator
= 시간에 따라 도착하는 여러 결과&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;46500&quot; data-start=&quot;46497&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;46543&quot; data-start=&quot;46502&quot; data-section-id=&quot;c5vl0e&quot; data-ke-size=&quot;size26&quot;&gt;14. 종합 예제: 필수 데이터, 선택 데이터, timeout, 취소&lt;/h2&gt;
&lt;p data-end=&quot;46570&quot; data-start=&quot;46545&quot; data-ke-size=&quot;size16&quot;&gt;대시보드에 다음 데이터가 필요하다고 가정하자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;사용자 정보
= 필수

주문 정보
= 필수

추천 정보
= 선택

분석 정보
= 선택

전체 timeout
= 5초

페이지 이탈
= 모든 요청 취소&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;46691&quot; data-start=&quot;46668&quot; data-ke-size=&quot;size16&quot;&gt;HTTP 오류를 표현하는 클래스를 만든다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;class HttpError extends Error {
  constructor(
    status,
    url,
  ) {
    super(`HTTP ${status}: ${url}`);

    this.name = &quot;HttpError&quot;;
    this.status = status;
    this.url = url;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;46914&quot; data-start=&quot;46897&quot; data-ke-size=&quot;size16&quot;&gt;JSON 요청 함수를 작성한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;async function fetchJson(
  url,
  {
    signal,
  },
) {
  const response = await fetch(url, {
    signal,
  });

  if (!response.ok) {
    throw new HttpError(
      response.status,
      url,
    );
  }

  return response.json();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;47184&quot; data-start=&quot;47163&quot; data-ke-size=&quot;size16&quot;&gt;선택 데이터의 실패를 값으로 변환한다.&lt;/p&gt;
&lt;p data-end=&quot;47204&quot; data-start=&quot;47186&quot; data-ke-size=&quot;size16&quot;&gt;단, 전체 취소는 삼키지 않는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;async function optional(
  operation,
  {
    signal,
  },
) {
  try {
    return {
      ok: true,
      value: await operation(),
    };
  } catch (error) {
    if (signal.aborted) {
      throw signal.reason ?? error;
    }

    return {
      ok: false,
      error,
    };
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;47517&quot; data-start=&quot;47501&quot; data-ke-size=&quot;size16&quot;&gt;대시보드 로더는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;async function loadDashboard(
  userId,
  {
    signal,
    timeoutMs = 5000,
  } = {},
) {
  const signals = [
    AbortSignal.timeout(timeoutMs),
  ];

  if (signal) {
    signals.push(signal);
  }

  const requestSignal =
    AbortSignal.any(signals);

  const userPromise = fetchJson(
    `/api/users/${userId}`,
    {
      signal: requestSignal,
    },
  );

  const ordersPromise = fetchJson(
    `/api/users/${userId}/orders`,
    {
      signal: requestSignal,
    },
  );

  const recommendationsPromise = optional(
    () =&amp;gt; {
      return fetchJson(
        `/api/users/${userId}/recommendations`,
        {
          signal: requestSignal,
        },
      );
    },
    {
      signal: requestSignal,
    },
  );

  const analyticsPromise = optional(
    () =&amp;gt; {
      return fetchJson(
        `/api/users/${userId}/analytics`,
        {
          signal: requestSignal,
        },
      );
    },
    {
      signal: requestSignal,
    },
  );

  const [
    user,
    orders,
    recommendations,
    analytics,
  ] = await Promise.all([
    userPromise,
    ordersPromise,
    recommendationsPromise,
    analyticsPromise,
  ]);

  return {
    user,
    orders,

    recommendations:
      recommendations.ok
        ? recommendations.value
        : [],

    analytics:
      analytics.ok
        ? analytics.value
        : null,

    warnings: [
      recommendations.ok
        ? null
        : recommendations.error,

      analytics.ok
        ? null
        : analytics.error,
    ].filter(Boolean),
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;49092&quot; data-start=&quot;49063&quot; data-ke-size=&quot;size16&quot;&gt;React에서는 컴포넌트 생명주기와 연결할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;useEffect(() =&amp;gt; {
  const controller = new AbortController();

  loadDashboard(userId, {
    signal: controller.signal,
  })
    .then(setDashboard)
    .catch((error) =&amp;gt; {
      if (controller.signal.aborted) {
        return;
      }

      setError(error);
    });

  return () =&amp;gt; {
    controller.abort(
      new DOMException(
        &quot;Page changed&quot;,
        &quot;AbortError&quot;,
      ),
    );
  };
}, [userId]);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;49541&quot; data-start=&quot;49519&quot; data-ke-size=&quot;size16&quot;&gt;이 설계에는 다음 정책이 명시되어 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;독립 작업은 동시에 시작한다.

필수 데이터 실패
&amp;rarr; 전체 작업 실패

선택 데이터 실패
&amp;rarr; 부분 결과와 warning 반환

페이지 이탈
&amp;rarr; 모든 요청 취소

5초 초과
&amp;rarr; 모든 요청 timeout

취소
&amp;rarr; 선택 데이터 실패로 오인하지 않음&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;49711&quot; data-start=&quot;49692&quot; data-ke-size=&quot;size16&quot;&gt;중요한 것은 사용한 문법이 아니다.&lt;/p&gt;
&lt;p data-end=&quot;49739&quot; data-start=&quot;49713&quot; data-ke-size=&quot;size16&quot;&gt;다음 설계 결정을 코드로 드러낸 것이 핵심이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;작업 소유자는 누구인가?
어떤 실패가 전체 실패인가?
부분 실패를 허용하는가?
작업은 언제 종료되어야 하는가?
취소는 어디까지 전달되는가?&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;49834&quot; data-start=&quot;49831&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;49854&quot; data-start=&quot;49836&quot; data-section-id=&quot;zxb1yv&quot; data-ke-size=&quot;size26&quot;&gt;15. 실무 설계 체크리스트&lt;/h2&gt;
&lt;p data-end=&quot;49887&quot; data-start=&quot;49856&quot; data-ke-size=&quot;size16&quot;&gt;비동기 API를 설계할 때 다음 질문을 확인할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;이 함수 호출 시 작업이 즉시 시작되는가?

같은 Promise를 재사용하는가,
매번 새 작업을 만드는가?

timeout은 누가 책임지는가?

외부에서 작업을 취소할 수 있는가?

부모 취소가 자식 작업에 전파되는가?

필수 작업과 선택 작업은 무엇인가?

부분 실패를 허용하는가?

어떤 오류를 재시도할 수 있는가?

재시도해도 부수 효과가 중복되지 않는가?

동시에 몇 개의 작업까지 실행할 것인가?

영원히 pending될 가능성이 있는가?

handler가 큰 객체를 오래 참조하지 않는가?

하나의 결과인가,
여러 값의 흐름인가?&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;50207&quot; data-start=&quot;50204&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;50215&quot; data-start=&quot;50209&quot; data-section-id=&quot;1h9nj85&quot; data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-end=&quot;50248&quot; data-start=&quot;50217&quot; data-ke-size=&quot;size16&quot;&gt;Promise는 결과와 오류를 표현하는 강력한 추상화다.&lt;/p&gt;
&lt;p data-end=&quot;50288&quot; data-start=&quot;50250&quot; data-ke-size=&quot;size16&quot;&gt;그러나 Promise만으로는 완전한 비동기 아키텍처를 만들 수 없다.&lt;/p&gt;
&lt;p data-end=&quot;50311&quot; data-start=&quot;50290&quot; data-ke-size=&quot;size16&quot;&gt;실무에서는 다음 층이 추가로 필요하다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;취소
timeout
작업 소유권
오류 분류
재시도
멱등성
동시성 제한
메모리 관리
backpressure&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;50427&quot; data-start=&quot;50384&quot; data-ke-size=&quot;size16&quot;&gt;좋은 비동기 코드는 단순히 모든 함수를 async로 만드는 코드가 아니다.&lt;/p&gt;
&lt;p data-end=&quot;50491&quot; data-start=&quot;50429&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;작업이 언제 시작되고, 누가 소유하며, 언제 종료되고, 실패와 취소가 어디로 전파되는지를 명시한 코드다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-end=&quot;50501&quot; data-start=&quot;50493&quot; data-section-id=&quot;3c0sv5&quot; data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;50842&quot; data-start=&quot;50503&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;50595&quot; data-start=&quot;50503&quot; data-section-id=&quot;axwieq&quot;&gt;WHATWG DOM Standard, AbortController와 AbortSignal.&lt;/li&gt;
&lt;li data-end=&quot;50682&quot; data-start=&quot;50596&quot; data-section-id=&quot;19z9mof&quot;&gt;WHATWG Fetch Standard, Request와 AbortSignal.&lt;/li&gt;
&lt;li data-end=&quot;50764&quot; data-start=&quot;50683&quot; data-section-id=&quot;pgoxba&quot;&gt;ECMA-262, Promise 반응 목록과 Await 실행 컨텍스트.&lt;/li&gt;
&lt;li data-is-last-node=&quot;&quot; data-end=&quot;50842&quot; data-start=&quot;50765&quot; data-section-id=&quot;1022p3b&quot;&gt;ECMA-262, Async Iterator Interface.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Frontend/JavaScript</category>
      <author>토리나나</author>
      <guid isPermaLink="true">https://khys.tistory.com/115</guid>
      <comments>https://khys.tistory.com/115#entry115comment</comments>
      <pubDate>Thu, 25 Jun 2026 08:00:48 +0900</pubDate>
    </item>
    <item>
      <title>Promise 동시성 설계: 순차 실행, 조합자, Critical Path</title>
      <link>https://khys.tistory.com/114</link>
      <description>&lt;blockquote data-end=&quot;22716&quot; data-start=&quot;22682&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;22716&quot; data-start=&quot;22684&quot; data-ke-size=&quot;size16&quot;&gt;Promise와 async/await 깊이 이해하기 3/4&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-section-id=&quot;1fudykl&quot; data-end=&quot;22725&quot; data-start=&quot;22718&quot; data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-end=&quot;22751&quot; data-start=&quot;22727&quot; data-ke-size=&quot;size16&quot;&gt;async/await 코드는 읽기 쉽다.&lt;/p&gt;
&lt;p data-end=&quot;22797&quot; data-start=&quot;22753&quot; data-ke-size=&quot;size16&quot;&gt;그러나 너무 자연스럽게 읽히기 때문에 오히려 불필요한 순차 실행을 만들기 쉽다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;const user = await fetchUser();
const settings = await fetchSettings();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;22925&quot; data-start=&quot;22882&quot; data-ke-size=&quot;size16&quot;&gt;두 작업이 서로 독립적이라면 이 코드는 존재하지 않는 의존 관계를 만들어낸다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;fetchUser 완료
        &amp;darr;
fetchSettings 시작&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;23027&quot; data-start=&quot;22979&quot; data-ke-size=&quot;size16&quot;&gt;Promise 동시성 설계의 핵심은 Promise.all()을 외우는 것이 아니다.&lt;/p&gt;
&lt;p data-end=&quot;23044&quot; data-start=&quot;23029&quot; data-ke-size=&quot;size16&quot;&gt;다음 질문에 답하는 것이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;어떤 작업이 다른 작업의 결과를 필요로 하는가?
어떤 작업은 동시에 시작할 수 있는가?
실패했을 때 나머지 작업은 어떻게 해야 하는가?
동시에 몇 개까지 실행해도 되는가?&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;23157&quot; data-start=&quot;23154&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-section-id=&quot;hzfgwu&quot; data-end=&quot;23173&quot; data-start=&quot;23159&quot; data-ke-size=&quot;size26&quot;&gt;1. 동시성과 병렬성&lt;/h2&gt;
&lt;p data-end=&quot;23196&quot; data-start=&quot;23175&quot; data-ke-size=&quot;size16&quot;&gt;동시성과 병렬성은 같은 개념이 아니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;동시성
= 여러 작업의 진행 구간이 겹칠 수 있도록 구조화하는 것

병렬성
= 여러 실행 주체가 같은 시간에 실제로 작업하는 것&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;23341&quot; data-start=&quot;23282&quot; data-ke-size=&quot;size16&quot;&gt;브라우저의 메인 JavaScript 실행은 일반적으로 한 번에 하나의 JavaScript 작업을 수행한다.&lt;/p&gt;
&lt;p data-end=&quot;23392&quot; data-start=&quot;23343&quot; data-ke-size=&quot;size16&quot;&gt;그러나 네트워크 요청처럼 외부 시스템에 위임된 작업은 여러 개가 동시에 진행될 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;const a = fetch(&quot;/api/a&quot;);
const b = fetch(&quot;/api/b&quot;);
const c = fetch(&quot;/api/c&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;23546&quot; data-start=&quot;23486&quot; data-ke-size=&quot;size16&quot;&gt;세 Promise를 만들었다고 JavaScript 코드 세 개가 메인 스레드에서 병렬 실행되는 것은 아니다.&lt;/p&gt;
&lt;p data-end=&quot;23578&quot; data-start=&quot;23548&quot; data-ke-size=&quot;size16&quot;&gt;하지만 세 네트워크 요청의 대기 시간은 겹칠 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;julia&quot;&gt;&lt;code&gt;JavaScript 병렬 실행
&amp;ne;

외부 I/O 작업의 동시 진행&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;23632&quot; data-start=&quot;23629&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-section-id=&quot;6qsuv5&quot; data-end=&quot;23658&quot; data-start=&quot;23634&quot; data-ke-size=&quot;size26&quot;&gt;2. 비동기 프로그램은 의존성 그래프다&lt;/h2&gt;
&lt;p data-end=&quot;23683&quot; data-start=&quot;23660&quot; data-ke-size=&quot;size16&quot;&gt;비동기 작업을 방향성 그래프로 표현해보자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;G = (V, E)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;23730&quot; data-start=&quot;23708&quot; data-ke-size=&quot;size16&quot;&gt;V는 작업이고 E는 의존 관계다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;dns&quot;&gt;&lt;code&gt;A &amp;rarr; B&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;23786&quot; data-start=&quot;23750&quot; data-ke-size=&quot;size16&quot;&gt;는 B를 시작하거나 완료하기 위해 A의 결과가 필요하다는 뜻이다.&lt;/p&gt;
&lt;p data-end=&quot;23812&quot; data-start=&quot;23788&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 코드는 실제 의존성이 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;const user = await fetchUser();

const orders = await fetchOrders(user.id);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;23935&quot; data-start=&quot;23901&quot; data-ke-size=&quot;size16&quot;&gt;fetchOrders()에는 user.id가 필요하다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;fetchUser &amp;rarr; fetchOrders&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;23987&quot; data-start=&quot;23973&quot; data-ke-size=&quot;size16&quot;&gt;이 순차 실행은 올바르다.&lt;/p&gt;
&lt;p data-end=&quot;24016&quot; data-start=&quot;23989&quot; data-ke-size=&quot;size16&quot;&gt;반면 다음 작업들이 서로 독립적이라고 가정해보자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;const locale = await fetchLocale();
const featureFlags = await fetchFeatureFlags();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;24129&quot; data-start=&quot;24113&quot; data-ke-size=&quot;size16&quot;&gt;코드는 다음 의존성을 만든다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;fetchLocale &amp;rarr; fetchFeatureFlags&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;24198&quot; data-start=&quot;24175&quot; data-ke-size=&quot;size16&quot;&gt;하지만 실제 요구사항에는 이 간선이 없다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;fetchLocale

fetchFeatureFlags&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;24276&quot; data-start=&quot;24243&quot; data-ke-size=&quot;size16&quot;&gt;성능 최적화는 단순히 await을 줄이는 작업이 아니다.&lt;/p&gt;
&lt;p data-end=&quot;24311&quot; data-start=&quot;24278&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실제로 존재하지 않는 의존 간선을 제거하는 작업&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr data-end=&quot;24316&quot; data-start=&quot;24313&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-section-id=&quot;1889tj2&quot; data-end=&quot;24337&quot; data-start=&quot;24318&quot; data-ke-size=&quot;size26&quot;&gt;3. Critical Path&lt;/h2&gt;
&lt;p data-end=&quot;24372&quot; data-start=&quot;24339&quot; data-ke-size=&quot;size16&quot;&gt;전체 작업 완료 시간은 가장 긴 의존 경로에 의해 제한된다.&lt;/p&gt;
&lt;p data-end=&quot;24387&quot; data-start=&quot;24374&quot; data-ke-size=&quot;size16&quot;&gt;단순화하면 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;전체 완료 시간
&amp;ge; 가장 긴 의존 경로의 작업 시간 합&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;24453&quot; data-start=&quot;24432&quot; data-ke-size=&quot;size16&quot;&gt;두 작업을 순차 실행하면 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;const a = await taskA();
const b = await taskB();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;24531&quot; data-start=&quot;24516&quot; data-ke-size=&quot;size16&quot;&gt;예상 시간은 다음에 가깝다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;T &amp;asymp; Ta + Tb&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;24578&quot; data-start=&quot;24557&quot; data-ke-size=&quot;size16&quot;&gt;두 작업을 함께 시작하면 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const aPromise = taskA();
const bPromise = taskB();

const [a, b] = await Promise.all([
  aPromise,
  bPromise,
]);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;24722&quot; data-start=&quot;24707&quot; data-ke-size=&quot;size16&quot;&gt;예상 시간은 다음에 가깝다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;T &amp;asymp; max(Ta, Tb) + 조정 비용&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;24787&quot; data-start=&quot;24760&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 두 요청에 각각 1초가 걸린다고 하자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;순차 실행
&amp;asymp; 2초

동시 시작
&amp;asymp; 1초&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;24898&quot; data-start=&quot;24824&quot; data-ke-size=&quot;size16&quot;&gt;실제 시간은 네트워크, 서버, 브라우저 요청 제한 등 여러 환경 요인의 영향을 받지만, 의존성 그래프 관점에서는 이 차이가 핵심이다.&lt;/p&gt;
&lt;hr data-end=&quot;24903&quot; data-start=&quot;24900&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-section-id=&quot;ehrsgm&quot; data-end=&quot;24929&quot; data-start=&quot;24905&quot; data-ke-size=&quot;size26&quot;&gt;4. 작업 시작과 결과 대기를 분리하라&lt;/h2&gt;
&lt;p data-end=&quot;24946&quot; data-start=&quot;24931&quot; data-ke-size=&quot;size16&quot;&gt;다음 코드는 순차 실행이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;const user = await fetchUser();
const settings = await fetchSettings();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;25076&quot; data-start=&quot;25031&quot; data-ke-size=&quot;size16&quot;&gt;fetchSettings() 호출 자체가 첫 번째 요청 완료 이후에 일어난다.&lt;/p&gt;
&lt;p data-end=&quot;25105&quot; data-start=&quot;25078&quot; data-ke-size=&quot;size16&quot;&gt;동시 실행을 원한다면 먼저 작업을 시작해야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;const userPromise = fetchUser();
const settingsPromise = fetchSettings();

const user = await userPromise;
const settings = await settingsPromise;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;25282&quot; data-start=&quot;25265&quot; data-ke-size=&quot;size16&quot;&gt;또는 다음처럼 결합할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const [user, settings] = await Promise.all([
  fetchUser(),
  fetchSettings(),
]);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;25424&quot; data-start=&quot;25378&quot; data-ke-size=&quot;size16&quot;&gt;중요한 사실은 Promise.all()이 작업을 시작하는 것이 아니라는 점이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;fetchUser();
fetchSettings();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;25489&quot; data-start=&quot;25467&quot; data-ke-size=&quot;size16&quot;&gt;함수를 호출하는 시점에 작업이 시작된다.&lt;/p&gt;
&lt;p data-end=&quot;25532&quot; data-start=&quot;25491&quot; data-ke-size=&quot;size16&quot;&gt;Promise.all()은 이미 시작된 작업들의 완료 조건을 결합한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;함수 호출
= 작업 시작

Promise.all
= 여러 결과의 완료 규칙 정의

await
= 결합된 결과를 현재 async 흐름에서 기다림&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;25629&quot; data-start=&quot;25626&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-section-id=&quot;149r2yx&quot; data-end=&quot;25652&quot; data-start=&quot;25631&quot; data-ke-size=&quot;size26&quot;&gt;5. 의존성과 독립성이 섞인 경우&lt;/h2&gt;
&lt;p data-end=&quot;25691&quot; data-start=&quot;25654&quot; data-ke-size=&quot;size16&quot;&gt;실무에서는 모든 작업이 완전히 독립적이거나 완전히 순차적이지 않다.&lt;/p&gt;
&lt;p data-end=&quot;25708&quot; data-start=&quot;25693&quot; data-ke-size=&quot;size16&quot;&gt;다음 요구사항을 생각해보자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;사용자 정보가 있어야 주문 정보를 요청할 수 있다.
설정 정보는 사용자 정보와 독립적이다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;25790&quot; data-start=&quot;25773&quot; data-ke-size=&quot;size16&quot;&gt;좋지 않은 구조는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;const user = await fetchUser();
const orders = await fetchOrders(user.id);
const settings = await fetchSettings();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;25938&quot; data-start=&quot;25918&quot; data-ke-size=&quot;size16&quot;&gt;전체 그래프가 불필요하게 직렬화된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;user &amp;rarr; orders &amp;rarr; settings&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;26005&quot; data-start=&quot;25977&quot; data-ke-size=&quot;size16&quot;&gt;더 나은 구조는 독립 작업을 먼저 시작하는 것이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;const userPromise = fetchUser();
const settingsPromise = fetchSettings();

const user = await userPromise;

const ordersPromise = fetchOrders(user.id);

const [orders, settings] = await Promise.all([
  ordersPromise,
  settingsPromise,
]);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;26270&quot; data-start=&quot;26258&quot; data-ke-size=&quot;size16&quot;&gt;그래프는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;fetchUser &amp;rarr; fetchOrders
       \
        독립적으로 fetchSettings 진행&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;26383&quot; data-start=&quot;26348&quot; data-ke-size=&quot;size16&quot;&gt;이 구조에서 critical path는 다음 둘 중 긴 경로다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;fetchUser + fetchOrders

fetchSettings&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;26439&quot; data-start=&quot;26436&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-section-id=&quot;rj1553&quot; data-end=&quot;26471&quot; data-start=&quot;26441&quot; data-ke-size=&quot;size26&quot;&gt;6. Promise 조합자는 실패 정책을 표현한다&lt;/h2&gt;
&lt;p data-end=&quot;26501&quot; data-start=&quot;26473&quot; data-ke-size=&quot;size16&quot;&gt;Promise 조합자는 단순한 편의 함수가 아니다.&lt;/p&gt;
&lt;p data-end=&quot;26535&quot; data-start=&quot;26503&quot; data-ke-size=&quot;size16&quot;&gt;각 조합자는 서로 다른 완료 조건과 실패 정책을 나타낸다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;조합자성공 조건실패 조건의미
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;26875&quot; data-start=&quot;26537&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;26875&quot; data-start=&quot;26584&quot;&gt;
&lt;tr data-end=&quot;26639&quot; data-start=&quot;26584&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;26600&quot; data-start=&quot;26584&quot;&gt;Promise.all&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;26616&quot; data-start=&quot;26600&quot;&gt;모든 입력 fulfill&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;26630&quot; data-start=&quot;26616&quot;&gt;하나라도 reject&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;26639&quot; data-start=&quot;26630&quot;&gt;모두 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;26732&quot; data-start=&quot;26640&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;26663&quot; data-start=&quot;26640&quot;&gt;Promise.allSettled&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;26678&quot; data-start=&quot;26663&quot;&gt;모든 입력 settle&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;26720&quot; data-start=&quot;26678&quot;&gt;입력 rejection으로는 집계 Promise가 reject되지 않음&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;26732&quot; data-start=&quot;26720&quot;&gt;모든 결과 관찰&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;26791&quot; data-start=&quot;26733&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;26749&quot; data-start=&quot;26733&quot;&gt;Promise.any&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;26764&quot; data-start=&quot;26749&quot;&gt;하나라도 fulfill&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;26776&quot; data-start=&quot;26764&quot;&gt;모두 reject&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;26791&quot; data-start=&quot;26776&quot;&gt;하나의 성공이면 충분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;26875&quot; data-start=&quot;26792&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;26809&quot; data-start=&quot;26792&quot;&gt;Promise.race&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;26825&quot; data-start=&quot;26809&quot;&gt;하나가 먼저 settle&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;26860&quot; data-start=&quot;26825&quot;&gt;첫 settlement가 rejection이면 reject&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;26875&quot; data-start=&quot;26860&quot;&gt;가장 빠른 결과 채택&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;27075&quot; data-start=&quot;26877&quot; data-ke-size=&quot;size16&quot;&gt;ECMAScript 명세도 각 조합자의 완료 규칙을 별도로 정의한다. Promise.all()은 하나라도 reject되면 거부되고, allSettled()는 모든 상태를 모으며, any()는 첫 성공 또는 전체 실패를, race()는 첫 settlement를 채택한다.&lt;/p&gt;
&lt;hr data-end=&quot;27080&quot; data-start=&quot;27077&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-section-id=&quot;yczsoy&quot; data-end=&quot;27108&quot; data-start=&quot;27082&quot; data-ke-size=&quot;size26&quot;&gt;7. Promise.all: 모두 필요하다&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const [user, orders, settings] = await Promise.all([
  fetchUser(),
  fetchOrders(),
  fetchSettings(),
]);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;27263&quot; data-start=&quot;27229&quot; data-ke-size=&quot;size16&quot;&gt;세 결과가 모두 있어야 다음 단계로 갈 수 있을 때 사용한다.&lt;/p&gt;
&lt;p data-end=&quot;27298&quot; data-start=&quot;27265&quot; data-ke-size=&quot;size16&quot;&gt;결과 배열은 작업 완료 순서가 아니라 입력 순서를 보존한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const slow = new Promise((resolve) =&amp;gt; {
  setTimeout(() =&amp;gt; resolve(&quot;slow&quot;), 100);
});

const fast = Promise.resolve(&quot;fast&quot;);

const result = await Promise.all([
  slow,
  fast,
]);

console.log(result);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;27525&quot; data-start=&quot;27514&quot; data-ke-size=&quot;size16&quot;&gt;출력은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[&quot;slow&quot;, &quot;fast&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;27591&quot; data-start=&quot;27555&quot; data-ke-size=&quot;size16&quot;&gt;fast가 먼저 완료되어도 결과 인덱스는 입력 순서를 따른다.&lt;/p&gt;
&lt;hr data-end=&quot;27596&quot; data-start=&quot;27593&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-section-id=&quot;12sil2q&quot; data-end=&quot;27639&quot; data-start=&quot;27598&quot; data-ke-size=&quot;size26&quot;&gt;8. Promise.allSettled: 부분 실패를 데이터로 다룬다&lt;/h2&gt;
&lt;p data-end=&quot;27704&quot; data-start=&quot;27641&quot; data-ke-size=&quot;size16&quot;&gt;모든 요청 결과를 확인해야 하고 일부 실패를 허용해야 한다면 Promise.allSettled()가 적합하다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const results = await Promise.allSettled([
  fetchMainContent(),
  fetchRecommendations(),
  fetchAdvertisement(),
]);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;27851&quot; data-start=&quot;27836&quot; data-ke-size=&quot;size16&quot;&gt;결과는 다음 형태를 가진다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;[
  {
    status: &quot;fulfilled&quot;,
    value: mainContent,
  },
  {
    status: &quot;rejected&quot;,
    reason: error,
  },
  {
    status: &quot;fulfilled&quot;,
    value: advertisement,
  },
]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;28052&quot; data-start=&quot;28038&quot; data-ke-size=&quot;size16&quot;&gt;처리 예시는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;for (const result of results) {
  if (result.status === &quot;fulfilled&quot;) {
    render(result.value);
    continue;
  }

  reportError(result.reason);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;28244&quot; data-start=&quot;28213&quot; data-ke-size=&quot;size16&quot;&gt;allSettled()는 실패를 없애는 것이 아니다.&lt;/p&gt;
&lt;p data-end=&quot;28283&quot; data-start=&quot;28246&quot; data-ke-size=&quot;size16&quot;&gt;실패를 예외 제어 흐름에서 &lt;b&gt;명시적인 결과 데이터&lt;/b&gt;로 바꿔준다.&lt;/p&gt;
&lt;hr data-end=&quot;28288&quot; data-start=&quot;28285&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-section-id=&quot;1osy1xe&quot; data-end=&quot;28322&quot; data-start=&quot;28290&quot; data-ke-size=&quot;size26&quot;&gt;9. Promise.any: 하나의 성공이면 충분하다&lt;/h2&gt;
&lt;p data-end=&quot;28352&quot; data-start=&quot;28324&quot; data-ke-size=&quot;size16&quot;&gt;여러 공급자 중 하나만 성공해도 되는 경우가 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;const data = await Promise.any([
  fetchFromPrimaryCDN(),
  fetchFromSecondaryCDN(),
  fetchFromBackupServer(),
]);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;28523&quot; data-start=&quot;28481&quot; data-ke-size=&quot;size16&quot;&gt;먼저 reject된 작업이 있어도 다른 작업의 성공 가능성을 계속 기다린다.&lt;/p&gt;
&lt;p data-end=&quot;28564&quot; data-start=&quot;28525&quot; data-ke-size=&quot;size16&quot;&gt;모든 작업이 실패하면 AggregateError로 reject된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;try {
  await Promise.any([
    Promise.reject(new Error(&quot;A failed&quot;)),
    Promise.reject(new Error(&quot;B failed&quot;)),
  ]);
} catch (error) {
  console.log(error instanceof AggregateError);
  console.log(error.errors);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;28851&quot; data-start=&quot;28794&quot; data-ke-size=&quot;size16&quot;&gt;Promise.any()는 &amp;ldquo;가장 빨리 끝난 작업&amp;rdquo;이 아니라 &amp;ldquo;가장 먼저 성공한 작업&amp;rdquo;을 선택한다.&lt;/p&gt;
&lt;hr data-end=&quot;28856&quot; data-start=&quot;28853&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-section-id=&quot;m0y97z&quot; data-end=&quot;28897&quot; data-start=&quot;28858&quot; data-ke-size=&quot;size26&quot;&gt;10. Promise.race: 첫 settlement를 채택한다&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const result = await Promise.race([
  operationA(),
  operationB(),
]);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;29009&quot; data-start=&quot;28982&quot; data-ke-size=&quot;size16&quot;&gt;race()는 첫 성공을 고르는 것이 아니다.&lt;/p&gt;
&lt;p data-end=&quot;29043&quot; data-start=&quot;29011&quot; data-ke-size=&quot;size16&quot;&gt;가장 먼저 settle된 Promise의 상태를 채택한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;첫 번째 Promise가 fulfill
&amp;rarr; 결과 fulfill

첫 번째 Promise가 reject
&amp;rarr; 결과 reject&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;29150&quot; data-start=&quot;29126&quot; data-ke-size=&quot;size16&quot;&gt;따라서 timeout 구현에 자주 사용된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function timeout(ms) {
  return new Promise((_, reject) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      reject(new Error(&quot;Timeout&quot;));
    }, ms);
  });
}

const result = await Promise.race([
  fetchData(),
  timeout(3000),
]);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;29410&quot; data-start=&quot;29376&quot; data-ke-size=&quot;size16&quot;&gt;그러나 이 코드는 fetchData()를 취소하지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;29458&quot; data-start=&quot;29412&quot; data-ke-size=&quot;size16&quot;&gt;호출자가 timeout 결과를 먼저 관찰할 뿐, 원래 작업은 계속 실행될 수 있다.&lt;/p&gt;
&lt;hr data-end=&quot;29463&quot; data-start=&quot;29460&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-section-id=&quot;1l5fjkr&quot; data-end=&quot;29500&quot; data-start=&quot;29465&quot; data-ke-size=&quot;size26&quot;&gt;11. fail-fast는 cancellation이 아니다&lt;/h2&gt;
&lt;p data-end=&quot;29561&quot; data-start=&quot;29502&quot; data-ke-size=&quot;size16&quot;&gt;Promise.all()은 하나의 입력이 reject되면 집계 Promise를 빠르게 reject한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;await Promise.all([
  saveUser(),
  saveOrder(),
  sendNotification(),
]);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;29697&quot; data-start=&quot;29649&quot; data-ke-size=&quot;size16&quot;&gt;saveOrder()가 먼저 실패해도 나머지 작업이 자동으로 중단되는 것은 아니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Promise.all 결과
&amp;rarr; 빠르게 reject

saveUser 작업
&amp;rarr; 이미 실행 중일 수 있음

sendNotification 작업
&amp;rarr; 계속 진행될 수 있음&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;29834&quot; data-start=&quot;29803&quot; data-ke-size=&quot;size16&quot;&gt;Promise.all()이 제공하는 것은 다음뿐이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;결과 관찰의 fail-fast&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;29878&quot; data-start=&quot;29865&quot; data-ke-size=&quot;size16&quot;&gt;다음은 제공하지 않는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;작업 취소
부수 효과 롤백
트랜잭션
원자성&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;29970&quot; data-start=&quot;29916&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 구조에서 주문 저장이 실패해도 사용자 저장이나 알림 전송이 이미 완료될 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;await Promise.all([
  updateUser(),
  createOrder(),
  sendEmail(),
]);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;30111&quot; data-start=&quot;30055&quot; data-ke-size=&quot;size16&quot;&gt;all-or-nothing이 필요하다면 Promise 조합자가 아니라 별도의 도메인 설계가 필요하다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;데이터베이스 트랜잭션
보상 작업
멱등성 키
사가 패턴
명시적 취소 신호&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;30168&quot; data-start=&quot;30165&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-section-id=&quot;ophj61&quot; data-end=&quot;30202&quot; data-start=&quot;30170&quot; data-ke-size=&quot;size26&quot;&gt;12. 조합자는 iterable을 동기적으로 소비한다&lt;/h2&gt;
&lt;p data-end=&quot;30232&quot; data-start=&quot;30204&quot; data-ke-size=&quot;size16&quot;&gt;다음 호출은 비동기적으로만 동작하는 것처럼 보인다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Promise.all(iterable);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;30320&quot; data-start=&quot;30268&quot; data-ke-size=&quot;size16&quot;&gt;그러나 Promise.all()은 전달받은 iterable을 현재 호출 흐름에서 순회한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const iterable = {
  *[Symbol.iterator]() {
    console.log(&quot;yield 1&quot;);
    yield Promise.resolve(1);

    console.log(&quot;yield 2&quot;);
    yield Promise.resolve(2);
  },
};

const resultPromise = Promise.all(iterable);

console.log(&quot;after&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;30582&quot; data-start=&quot;30571&quot; data-ke-size=&quot;size16&quot;&gt;출력은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;yield 1
yield 2
after&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;30671&quot; data-start=&quot;30618&quot; data-ke-size=&quot;size16&quot;&gt;iterator에서 값을 꺼내고 각 값을 Promise로 정규화하는 과정은 호출 중에 진행된다.&lt;/p&gt;
&lt;p data-end=&quot;30793&quot; data-start=&quot;30673&quot; data-ke-size=&quot;size16&quot;&gt;ECMAScript의 Promise 조합자 알고리즘도 전달받은 iterator를 순회하면서 각 원소를 Promise로 변환하고 반응을 연결한다.&lt;/p&gt;
&lt;p data-end=&quot;30835&quot; data-start=&quot;30795&quot; data-ke-size=&quot;size16&quot;&gt;따라서 무한 iterable을 넘기면 호출 자체가 끝나지 않을 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function* infinite() {
  while (true) {
    yield Promise.resolve(1);
  }
}

Promise.all(infinite());&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;30953&quot; data-start=&quot;30950&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-section-id=&quot;1rfkp4d&quot; data-end=&quot;30972&quot; data-start=&quot;30955&quot; data-ke-size=&quot;size26&quot;&gt;13. 무제한 동시성 문제&lt;/h2&gt;
&lt;p data-end=&quot;30986&quot; data-start=&quot;30974&quot; data-ke-size=&quot;size16&quot;&gt;다음 코드는 간결하다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const profiles = await Promise.all(
  users.map((user) =&amp;gt; {
    return fetchProfile(user.id);
  }),
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;31126&quot; data-start=&quot;31102&quot; data-ke-size=&quot;size16&quot;&gt;사용자가 10명이라면 문제가 없을 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;31165&quot; data-start=&quot;31128&quot; data-ke-size=&quot;size16&quot;&gt;10만 명이라면 호출 즉시 매우 많은 작업을 시작하려 할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;31204&quot; data-start=&quot;31167&quot; data-ke-size=&quot;size16&quot;&gt;병목은 Promise 객체 자체가 아니라 작업이 사용하는 자원이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;네트워크 연결
브라우저 요청 큐
서버 rate limit
메모리
데이터베이스 연결
외부 API 사용량
파일 디스크립터&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;31319&quot; data-start=&quot;31284&quot; data-ke-size=&quot;size16&quot;&gt;독립 작업이라고 해서 모두 동시에 시작하는 것이 최적은 아니다.&lt;/p&gt;
&lt;hr data-end=&quot;31324&quot; data-start=&quot;31321&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-section-id=&quot;1loi91q&quot; data-end=&quot;31343&quot; data-start=&quot;31326&quot; data-ke-size=&quot;size26&quot;&gt;14. 제한된 동시성 구현&lt;/h2&gt;
&lt;p data-end=&quot;31375&quot; data-start=&quot;31345&quot; data-ke-size=&quot;size16&quot;&gt;다음 함수는 동시에 실행할 worker 수를 제한한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async function mapConcurrent(
  items,
  limit,
  mapper,
) {
  if (!Number.isInteger(limit) || limit &amp;lt; 1) {
    throw new RangeError(
      &quot;limit은 1 이상의 정수여야 합니다.&quot;,
    );
  }

  const results = new Array(items.length);

  let nextIndex = 0;

  async function worker() {
    while (true) {
      const index = nextIndex;
      nextIndex += 1;

      if (index &amp;gt;= items.length) {
        return;
      }

      results[index] = await mapper(
        items[index],
        index,
      );
    }
  }

  const workerCount = Math.min(
    limit,
    items.length,
  );

  await Promise.all(
    Array.from(
      { length: workerCount },
      () =&amp;gt; worker(),
    ),
  );

  return results;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;32092&quot; data-start=&quot;32078&quot; data-ke-size=&quot;size16&quot;&gt;사용 방법은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const profiles = await mapConcurrent(
  users,
  5,
  async (user) =&amp;gt; {
    const response = await fetch(
      `/api/users/${user.id}`,
    );

    if (!response.ok) {
      throw new Error(
        `HTTP ${response.status}`,
      );
    }

    return response.json();
  },
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;32406&quot; data-start=&quot;32384&quot; data-ke-size=&quot;size16&quot;&gt;동시에 최대 다섯 개의 작업만 진행된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;worker 1: item 0 &amp;rarr; item 5 &amp;rarr; ...
worker 2: item 1 &amp;rarr; item 6 &amp;rarr; ...
worker 3: item 2 &amp;rarr; item 7 &amp;rarr; ...
worker 4: item 3 &amp;rarr; item 8 &amp;rarr; ...
worker 5: item 4 &amp;rarr; item 9 &amp;rarr; ...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;32610&quot; data-start=&quot;32580&quot; data-ke-size=&quot;size16&quot;&gt;결과는 완료 순서가 아니라 원래 입력 순서에 저장된다.&lt;/p&gt;
&lt;p data-end=&quot;32626&quot; data-start=&quot;32612&quot; data-ke-size=&quot;size16&quot;&gt;이 구현에도 한계는 있다.&lt;/p&gt;
&lt;p data-end=&quot;32713&quot; data-start=&quot;32628&quot; data-ke-size=&quot;size16&quot;&gt;mapper 하나가 실패하면 반환된 Promise.all()은 reject되지만, 이미 실행 중인 다른 mapper 작업은 자동으로 중단되지 않는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;동시성 제한
= 새 작업 시작 수 제한

실행 중 작업 취소
= 별도의 AbortSignal 필요&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;32802&quot; data-start=&quot;32782&quot; data-ke-size=&quot;size16&quot;&gt;취소와 작업 수명은 4부에서 다룬다.&lt;/p&gt;
&lt;hr data-end=&quot;32807&quot; data-start=&quot;32804&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-section-id=&quot;18pvh6z&quot; data-end=&quot;32840&quot; data-start=&quot;32809&quot; data-ke-size=&quot;size26&quot;&gt;15. Promise는 CPU 병렬화 도구가 아니다&lt;/h2&gt;
&lt;p data-end=&quot;32884&quot; data-start=&quot;32842&quot; data-ke-size=&quot;size16&quot;&gt;다음 코드는 세 계산을 Promise로 감쌌지만 병렬 계산을 만들지 않는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const results = await Promise.all([
  Promise.resolve().then(() =&amp;gt; heavyA()),
  Promise.resolve().then(() =&amp;gt; heavyB()),
  Promise.resolve().then(() =&amp;gt; heavyC()),
]);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;33110&quot; data-start=&quot;33063&quot; data-ke-size=&quot;size16&quot;&gt;각 handler는 여전히 같은 JavaScript 실행 주체에서 순서대로 실행된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;Promise 동시성
&amp;ne; CPU 병렬 실행&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;33185&quot; data-start=&quot;33148&quot; data-ke-size=&quot;size16&quot;&gt;CPU 집약 작업을 실제로 분산하려면 별도의 실행 주체가 필요하다.&lt;/p&gt;
&lt;p data-end=&quot;33222&quot; data-start=&quot;33187&quot; data-ke-size=&quot;size16&quot;&gt;브라우저에서는 대표적으로 Web Worker를 사용할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;네트워크 대기 시간 겹치기
&amp;rarr; Promise 동시성

CPU 계산을 다른 코어에서 실행
&amp;rarr; Worker 등 별도 실행 환경&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;33308&quot; data-start=&quot;33305&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-section-id=&quot;3h5ocx&quot; data-end=&quot;33326&quot; data-start=&quot;33310&quot; data-ke-size=&quot;size26&quot;&gt;16. 동시성 설계 원칙&lt;/h2&gt;
&lt;p data-end=&quot;33357&quot; data-start=&quot;33328&quot; data-ke-size=&quot;size16&quot;&gt;비동기 코드를 작성할 때 다음 순서로 생각하면 좋다.&lt;/p&gt;
&lt;h3 data-section-id=&quot;mvineq&quot; data-end=&quot;33385&quot; data-start=&quot;33359&quot; data-ke-size=&quot;size23&quot;&gt;첫째, 작업 사이의 실제 의존성을 찾는다&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;B가 A의 결과를 필요로 하는가?&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;33434&quot; data-start=&quot;33418&quot; data-ke-size=&quot;size16&quot;&gt;필요하다면 순차 실행이 맞다.&lt;/p&gt;
&lt;h3 data-section-id=&quot;1hk9ejz&quot; data-end=&quot;33458&quot; data-start=&quot;33436&quot; data-ke-size=&quot;size23&quot;&gt;둘째, 독립 작업은 먼저 시작한다&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;const aPromise = taskA();
const bPromise = taskB();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-section-id=&quot;z1vkje&quot; data-end=&quot;33550&quot; data-start=&quot;33523&quot; data-ke-size=&quot;size23&quot;&gt;셋째, 요구사항에 맞는 완료 정책을 고른다&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;모두 필요
&amp;rarr; Promise.all

모든 성공과 실패를 확인
&amp;rarr; Promise.allSettled

하나의 성공이면 충분
&amp;rarr; Promise.any

첫 결과만 필요
&amp;rarr; Promise.race&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-section-id=&quot;xgrnil&quot; data-end=&quot;33702&quot; data-start=&quot;33672&quot; data-ke-size=&quot;size23&quot;&gt;넷째, 동시 실행 수를 제한해야 하는지 판단한다&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;데이터 수가 작고 통제됨
&amp;rarr; Promise.all 가능

데이터 수가 크거나 외부 제한 존재
&amp;rarr; worker pool 또는 queue&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-section-id=&quot;1g8bzpf&quot; data-end=&quot;33826&quot; data-start=&quot;33791&quot; data-ke-size=&quot;size23&quot;&gt;다섯째, 실패 시 실행 중인 작업을 어떻게 할지 결정한다&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;계속 진행
중단
부분 결과 사용
재시도
보상 작업 실행&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;33874&quot; data-start=&quot;33871&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-section-id=&quot;1h9nj85&quot; data-end=&quot;33882&quot; data-start=&quot;33876&quot; data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-end=&quot;33917&quot; data-start=&quot;33884&quot; data-ke-size=&quot;size16&quot;&gt;Promise 동시성 설계의 핵심은 문법이 아니라 그래프다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;작업
의존 관계
실패 전파
자원 제한
완료 조건&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;33980&quot; data-start=&quot;33958&quot; data-ke-size=&quot;size16&quot;&gt;await은 동시성을 만들지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;34012&quot; data-start=&quot;33982&quot; data-ke-size=&quot;size16&quot;&gt;Promise.all()도 작업을 시작하지 않는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;작업을 시작하는 것
= 비동기 함수를 호출하는 것

결과를 결합하는 것
= Promise 조합자를 사용하는 것

현재 흐름을 중단하는 것
= await하는 것&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;34163&quot; data-start=&quot;34115&quot; data-ke-size=&quot;size16&quot;&gt;좋은 비동기 코드는 단순히 Promise.all()을 많이 사용하는 코드가 아니다.&lt;/p&gt;
&lt;p data-end=&quot;34236&quot; data-start=&quot;34165&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실제 의존 관계만 남기고, 시스템이 감당할 수 있는 범위 안에서 작업을 진행하며, 실패 정책을 명시적으로 표현한 코드다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-section-id=&quot;3c0sv5&quot; data-end=&quot;34246&quot; data-start=&quot;34238&quot; data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;34419&quot; data-start=&quot;34248&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-section-id=&quot;15b89ww&quot; data-end=&quot;34328&quot; data-start=&quot;34248&quot;&gt;ECMA-262, Promise Concurrency Methods.&lt;/li&gt;
&lt;li data-section-id=&quot;14qmshe&quot; data-end=&quot;34419&quot; data-start=&quot;34329&quot;&gt;ECMA-262, Promise Objects 및 Promise Reaction 처리.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Frontend/JavaScript</category>
      <category>Async</category>
      <category>await</category>
      <category>Promise</category>
      <author>토리나나</author>
      <guid isPermaLink="true">https://khys.tistory.com/114</guid>
      <comments>https://khys.tistory.com/114#entry114comment</comments>
      <pubDate>Wed, 24 Jun 2026 18:10:15 +0900</pubDate>
    </item>
    <item>
      <title>async/await의 실행 원리: Job, 마이크로태스크, 실행 컨텍스트</title>
      <link>https://khys.tistory.com/113</link>
      <description>&lt;blockquote data-end=&quot;11994&quot; data-start=&quot;11960&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;11994&quot; data-start=&quot;11962&quot; data-ke-size=&quot;size16&quot;&gt;Promise와 async/await 깊이 이해하기 2/4&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-end=&quot;12003&quot; data-start=&quot;11996&quot; data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-end=&quot;12052&quot; data-start=&quot;12005&quot; data-ke-size=&quot;size16&quot;&gt;async/await은 Promise 코드를 동기 코드처럼 읽을 수 있게 만든다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;async function loadUser() {
  const response = await fetch(&quot;/api/user&quot;);
  const user = await response.json();

  return user;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;12245&quot; data-start=&quot;12194&quot; data-ke-size=&quot;size16&quot;&gt;하지만 코드가 동기적으로 &lt;b&gt;보이는 것&lt;/b&gt;과 실제로 동기적으로 &lt;b&gt;실행되는 것&lt;/b&gt;은 다르다.&lt;/p&gt;
&lt;p data-end=&quot;12269&quot; data-start=&quot;12247&quot; data-ke-size=&quot;size16&quot;&gt;await은 스레드를 멈추지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;12290&quot; data-start=&quot;12271&quot; data-ke-size=&quot;size16&quot;&gt;Promise를 제거하지도 않는다.&lt;/p&gt;
&lt;p data-end=&quot;12313&quot; data-start=&quot;12292&quot; data-ke-size=&quot;size16&quot;&gt;실제로는 다음과 같은 과정이 일어난다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async 함수 실행
&amp;rarr; await 지점에서 실행 컨텍스트 중단
&amp;rarr; Promise 반응 등록
&amp;rarr; 호출자에게 제어권 반환
&amp;rarr; Promise 완료
&amp;rarr; Job을 통해 실행 컨텍스트 재개&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;12483&quot; data-start=&quot;12428&quot; data-ke-size=&quot;size16&quot;&gt;이를 정확히 이해하려면 JavaScript 언어 명세와 브라우저 호스트 환경을 분리해서 봐야 한다.&lt;/p&gt;
&lt;hr data-end=&quot;12488&quot; data-start=&quot;12485&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;12523&quot; data-start=&quot;12490&quot; data-ke-size=&quot;size26&quot;&gt;1. ECMAScript와 브라우저는 서로 다른 층이다&lt;/h2&gt;
&lt;p data-end=&quot;12584&quot; data-start=&quot;12525&quot; data-ke-size=&quot;size16&quot;&gt;JavaScript의 Promise 실행 순서를 설명할 때 흔히 &amp;ldquo;마이크로태스크 큐에 들어간다&amp;rdquo;고 말한다.&lt;/p&gt;
&lt;p data-end=&quot;12617&quot; data-start=&quot;12586&quot; data-ke-size=&quot;size16&quot;&gt;실무적으로는 맞지만, 명세 수준에서는 두 층이 존재한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;ECMAScript
= Promise와 Promise Job의 의미를 정의

HTML 호스트 환경
= Job을 마이크로태스크 큐와 이벤트 루프에 통합&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;12756&quot; data-start=&quot;12715&quot; data-ke-size=&quot;size16&quot;&gt;ECMAScript는 Promise 반응을 실행할 &lt;b&gt;Job&lt;/b&gt;을 만든다.&lt;/p&gt;
&lt;p data-end=&quot;12816&quot; data-start=&quot;12758&quot; data-ke-size=&quot;size16&quot;&gt;그 Job을 실제로 언제 실행할지는 HostEnqueuePromiseJob이라는 호스트 훅에 맡긴다.&lt;/p&gt;
&lt;p data-end=&quot;12900&quot; data-start=&quot;12818&quot; data-ke-size=&quot;size16&quot;&gt;브라우저에서는 HTML 명세가 이 Job을 마이크로태스크 처리 모델과 연결한다.&lt;/p&gt;
&lt;p data-end=&quot;12920&quot; data-start=&quot;12902&quot; data-ke-size=&quot;size16&quot;&gt;따라서 다음 표현이 더 정확하다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;cos&quot;&gt;&lt;code&gt;Promise handler는 ECMAScript의 Promise Reaction Job이다.

브라우저에서는 이 Job이 마이크로태스크로 처리된다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;13075&quot; data-start=&quot;13018&quot; data-ke-size=&quot;size16&quot;&gt;이 분리 때문에 브라우저와 다른 JavaScript 런타임의 세부 큐 정책이 완전히 같을 필요는 없다.&lt;/p&gt;
&lt;hr data-end=&quot;13080&quot; data-start=&quot;13077&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;13103&quot; data-start=&quot;13082&quot; data-ke-size=&quot;size26&quot;&gt;2. task와 microtask&lt;/h2&gt;
&lt;p data-end=&quot;13131&quot; data-start=&quot;13105&quot; data-ke-size=&quot;size16&quot;&gt;브라우저 이벤트 루프를 단순화하면 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;task 하나 실행
&amp;rarr; JavaScript 호출 스택이 비워짐
&amp;rarr; microtask checkpoint 수행
&amp;rarr; 렌더링 기회가 있으면 렌더링
&amp;rarr; 다음 task 실행&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;13256&quot; data-start=&quot;13237&quot; data-ke-size=&quot;size16&quot;&gt;대표적인 task에는 다음이 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;초기 스크립트 실행
setTimeout callback
사용자 입력 이벤트
네트워크 이벤트
메시지 이벤트&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;13353&quot; data-start=&quot;13329&quot; data-ke-size=&quot;size16&quot;&gt;대표적인 microtask에는 다음이 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Promise reaction
queueMicrotask callback
일부 DOM 반응&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;13477&quot; data-start=&quot;13418&quot; data-ke-size=&quot;size16&quot;&gt;HTML 명세의 microtask checkpoint는 큐에 있는 마이크로태스크를 하나만 실행하지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;13589&quot; data-start=&quot;13479&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;큐가 빌 때까지 계속 실행한다.&lt;/b&gt; 실행 중 새로운 마이크로태스크가 추가되면 그 작업도 같은 checkpoint에서 처리된다.&lt;/p&gt;
&lt;hr data-end=&quot;13594&quot; data-start=&quot;13591&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;13613&quot; data-start=&quot;13596&quot; data-ke-size=&quot;size26&quot;&gt;3. 기본 실행 순서 분석&lt;/h2&gt;
&lt;p data-end=&quot;13625&quot; data-start=&quot;13615&quot; data-ke-size=&quot;size16&quot;&gt;다음 코드를 보자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;console.log(&quot;A&quot;);

setTimeout(() =&amp;gt; {
  console.log(&quot;D&quot;);
}, 0);

Promise.resolve()
  .then(() =&amp;gt; {
    console.log(&quot;B&quot;);
  })
  .then(() =&amp;gt; {
    console.log(&quot;C&quot;);
  });

console.log(&quot;E&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;13839&quot; data-start=&quot;13828&quot; data-ke-size=&quot;size16&quot;&gt;출력은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;A
E
B
C
D&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;13881&quot; data-start=&quot;13863&quot; data-ke-size=&quot;size16&quot;&gt;실행 흐름을 나누면 다음과 같다.&lt;/p&gt;
&lt;h3 data-end=&quot;13894&quot; data-start=&quot;13883&quot; data-ke-size=&quot;size23&quot;&gt;현재 task&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;console.log(&quot;A&quot;)
setTimeout 등록
첫 번째 then 반응 등록
console.log(&quot;E&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;14018&quot; data-start=&quot;13972&quot; data-ke-size=&quot;size16&quot;&gt;현재 task가 끝났을 때 마이크로태스크 큐에는 첫 번째 then 반응이 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;apache&quot;&gt;&lt;code&gt;microtask queue: [B]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;14088&quot; data-start=&quot;14053&quot; data-ke-size=&quot;size16&quot;&gt;B가 실행되면서 다음 then 반응인 C가 추가된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;apache&quot;&gt;&lt;code&gt;microtask queue: [C]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;14164&quot; data-start=&quot;14123&quot; data-ke-size=&quot;size16&quot;&gt;큐가 빌 때까지 실행하므로 C도 같은 checkpoint에서 처리된다.&lt;/p&gt;
&lt;p data-end=&quot;14208&quot; data-start=&quot;14166&quot; data-ke-size=&quot;size16&quot;&gt;그다음 새로운 task인 setTimeout callback이 실행된다.&lt;/p&gt;
&lt;hr data-end=&quot;14213&quot; data-start=&quot;14210&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;14247&quot; data-start=&quot;14215&quot; data-ke-size=&quot;size26&quot;&gt;4. Promise 체인은 원자적으로 실행되지 않는다&lt;/h2&gt;
&lt;p data-end=&quot;14259&quot; data-start=&quot;14249&quot; data-ke-size=&quot;size16&quot;&gt;다음 코드를 보자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;Promise.resolve()
  .then(() =&amp;gt; {
    console.log(&quot;A&quot;);
  })
  .then(() =&amp;gt; {
    console.log(&quot;C&quot;);
  });

Promise.resolve().then(() =&amp;gt; {
  console.log(&quot;B&quot;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;14447&quot; data-start=&quot;14433&quot; data-ke-size=&quot;size16&quot;&gt;출력 결과는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;A
B
C&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;14494&quot; data-start=&quot;14467&quot; data-ke-size=&quot;size16&quot;&gt;처음 등록되는 마이크로태스크는 A와 B다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;apache&quot;&gt;&lt;code&gt;queue: [A, B]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;14545&quot; data-start=&quot;14522&quot; data-ke-size=&quot;size16&quot;&gt;A를 실행한 후에야 C가 등록된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;apache&quot;&gt;&lt;code&gt;queue: [B, C]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;14596&quot; data-start=&quot;14573&quot; data-ke-size=&quot;size16&quot;&gt;따라서 B가 C보다 먼저 실행된다.&lt;/p&gt;
&lt;p data-end=&quot;14630&quot; data-start=&quot;14598&quot; data-ke-size=&quot;size16&quot;&gt;Promise 체인 전체는 하나의 원자적인 작업이 아니다.&lt;/p&gt;
&lt;p data-end=&quot;14674&quot; data-start=&quot;14632&quot; data-ke-size=&quot;size16&quot;&gt;각 then() 단계는 별도의 Promise Reaction Job이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;then 1
&amp;rarr; 하나의 Job

then 2
&amp;rarr; 앞 단계가 완료된 뒤 등록되는 또 다른 Job&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;14786&quot; data-start=&quot;14741&quot; data-ke-size=&quot;size16&quot;&gt;이 때문에 서로 다른 Promise 체인의 연속성이 중간에 섞여 실행될 수 있다.&lt;/p&gt;
&lt;hr data-end=&quot;14791&quot; data-start=&quot;14788&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;14817&quot; data-start=&quot;14793&quot; data-ke-size=&quot;size26&quot;&gt;5. 마이크로태스크 starvation&lt;/h2&gt;
&lt;p data-end=&quot;14869&quot; data-start=&quot;14819&quot; data-ke-size=&quot;size16&quot;&gt;마이크로태스크는 우선순위가 높아 보이지만, 과도하게 생성하면 이벤트 루프를 굶길 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;delphi&quot;&gt;&lt;code&gt;function spin() {
  queueMicrotask(spin);
}

spin();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;14963&quot; data-start=&quot;14935&quot; data-ke-size=&quot;size16&quot;&gt;각 마이크로태스크가 다음 마이크로태스크를 예약한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;microtask 실행
&amp;rarr; 새로운 microtask 추가
&amp;rarr; 큐가 비지 않음
&amp;rarr; 다음 task로 이동하지 못함&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;15061&quot; data-start=&quot;15039&quot; data-ke-size=&quot;size16&quot;&gt;그 결과 다음 작업들이 지연될 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;사용자 입력 처리
타이머 callback
화면 렌더링
기타 task&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;15146&quot; data-start=&quot;15113&quot; data-ke-size=&quot;size16&quot;&gt;다음 코드도 UI에 제어권을 양보하는 확실한 방법은 아니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async function processItems(items) {
  for (const item of items) {
    heavyCalculation(item);
    await Promise.resolve();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;15337&quot; data-start=&quot;15289&quot; data-ke-size=&quot;size16&quot;&gt;await Promise.resolve() 이후의 실행은 마이크로태스크로 재개된다.&lt;/p&gt;
&lt;p data-end=&quot;15372&quot; data-start=&quot;15339&quot; data-ke-size=&quot;size16&quot;&gt;다음 task나 렌더링 기회로 반드시 넘어가는 것이 아니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;await Promise.resolve()
= 마이크로태스크 경계

렌더링에 양보
= 반드시 보장되지 않음&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;15485&quot; data-start=&quot;15446&quot; data-ke-size=&quot;size16&quot;&gt;CPU 집약 작업을 처리해야 한다면 다음 선택지가 더 적합할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;작업을 작은 task 단위로 분할
requestAnimationFrame 활용
스케줄러 사용
Web Worker에서 계산&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;15607&quot; data-start=&quot;15567&quot; data-ke-size=&quot;size16&quot;&gt;Promise는 CPU 작업을 별도 스레드에서 실행해주는 도구가 아니다.&lt;/p&gt;
&lt;hr data-end=&quot;15612&quot; data-start=&quot;15609&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;15634&quot; data-start=&quot;15614&quot; data-ke-size=&quot;size26&quot;&gt;6. await는 무엇을 하는가&lt;/h2&gt;
&lt;p data-end=&quot;15668&quot; data-start=&quot;15636&quot; data-ke-size=&quot;size16&quot;&gt;await value의 의미를 단순화하면 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. value를 Promise 형태로 정규화한다.
2. 현재 async 실행 컨텍스트를 중단한다.
3. 성공 시 재개할 반응을 등록한다.
4. 실패 시 재개할 반응을 등록한다.
5. 호출자에게 제어권을 반환한다.
6. Promise가 완료되면 Job을 통해 중단 지점 이후를 재개한다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;16046&quot; data-start=&quot;15843&quot; data-ke-size=&quot;size16&quot;&gt;ECMAScript의 Await(value) 추상 연산은 값을 intrinsic Promise로 정규화하고, 현재 async 실행 컨텍스트를 캡처한 fulfillment&amp;middot;rejection 함수를 만든다. 이후 PerformPromiseThen으로 반응을 등록하고 현재 컨텍스트를 중단한다.&lt;/p&gt;
&lt;p data-end=&quot;16093&quot; data-start=&quot;16048&quot; data-ke-size=&quot;size16&quot;&gt;다음 코드에서 중요한 것은 await이 현재 스레드를 멈추지 않는다는 점이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async function load() {
  const result = await fetchData();

  return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;16238&quot; data-start=&quot;16186&quot; data-ke-size=&quot;size16&quot;&gt;fetchData()가 완료될 때까지 JavaScript 엔진 전체가 멈추는 것이 아니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;load 함수의 현재 실행만 중단된다.
이벤트 루프는 다른 작업을 처리할 수 있다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;16302&quot; data-start=&quot;16299&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;16342&quot; data-start=&quot;16304&quot; data-ke-size=&quot;size26&quot;&gt;7. async 함수는 첫 await 전까지 동기적으로 실행된다&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;console.log(1);

async function run() {
  console.log(2);

  await 0;

  console.log(4);
}

run();

console.log(3);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;16482&quot; data-start=&quot;16471&quot; data-ke-size=&quot;size16&quot;&gt;출력은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1
2
3
4&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;16532&quot; data-start=&quot;16504&quot; data-ke-size=&quot;size16&quot;&gt;run() 호출 시 함수 본문은 즉시 시작한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;console.log(2);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;16581&quot; data-start=&quot;16561&quot; data-ke-size=&quot;size16&quot;&gt;까지는 현재 호출 스택에서 실행된다.&lt;/p&gt;
&lt;p data-end=&quot;16632&quot; data-start=&quot;16583&quot; data-ke-size=&quot;size16&quot;&gt;await 0을 만나면 함수가 중단되고 이후 코드는 Promise 반응으로 예약된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;현재 task

1
2
3

microtask

4&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;16697&quot; data-start=&quot;16675&quot; data-ke-size=&quot;size16&quot;&gt;따라서 다음 두 문장은 모두 부정확하다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;async 함수는 전부 비동기로 실행된다.
async 함수는 호출 즉시 나중으로 밀린다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;16776&quot; data-start=&quot;16761&quot; data-ke-size=&quot;size16&quot;&gt;정확한 설명은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;async 함수는 호출 즉시 실행을 시작한다.
첫 await 전까지는 동기적으로 실행된다.
await 이후의 연속성이 비동기적으로 재개된다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;16872&quot; data-start=&quot;16869&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;16904&quot; data-start=&quot;16874&quot; data-ke-size=&quot;size26&quot;&gt;8. 일반 값을 await해도 실행 순서가 바뀐다&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async function example() {
  console.log(&quot;before&quot;);

  await 42;

  console.log(&quot;after&quot;);
}

example();

console.log(&quot;outside&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;17057&quot; data-start=&quot;17046&quot; data-ke-size=&quot;size16&quot;&gt;출력은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;before
outside
after&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;17110&quot; data-start=&quot;17092&quot; data-ke-size=&quot;size16&quot;&gt;42는 비동기 계산이 아니다.&lt;/p&gt;
&lt;p data-end=&quot;17126&quot; data-start=&quot;17112&quot; data-ke-size=&quot;size16&quot;&gt;이미 존재하는 원시값이다.&lt;/p&gt;
&lt;p data-end=&quot;17188&quot; data-start=&quot;17128&quot; data-ke-size=&quot;size16&quot;&gt;하지만 await은 해당 값을 Promise처럼 정규화하고 후속 실행을 Promise 반응으로 연결한다.&lt;/p&gt;
&lt;p data-end=&quot;17220&quot; data-start=&quot;17190&quot; data-ke-size=&quot;size16&quot;&gt;따라서 다음 코드는 단순한 대입과 실행 시점이 다르다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const value = 42;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;const value = await 42;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;17325&quot; data-start=&quot;17286&quot; data-ke-size=&quot;size16&quot;&gt;결과값은 같지만 두 번째 코드에는 async 중단&amp;middot;재개 경계가 생긴다.&lt;/p&gt;
&lt;p data-end=&quot;17352&quot; data-start=&quot;17327&quot; data-ke-size=&quot;size16&quot;&gt;불필요한 await은 다음 변화를 만든다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;현재 실행의 분할
마이크로태스크 예약
다른 Promise 반응과의 실행 순서 변화
비동기 스택 경계 생성&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;17428&quot; data-start=&quot;17425&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;17466&quot; data-start=&quot;17430&quot; data-ke-size=&quot;size26&quot;&gt;9. async 함수는 항상 새로운 Promise를 반환한다&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const original = Promise.resolve(1);

async function identity() {
  return original;
}

const returned = identity();

console.log(returned === original); // false
console.log(await returned);        // 1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;17734&quot; data-start=&quot;17683&quot; data-ke-size=&quot;size16&quot;&gt;identity()가 반환한 Promise와 original은 동일한 객체가 아니다.&lt;/p&gt;
&lt;p data-end=&quot;17786&quot; data-start=&quot;17736&quot; data-ke-size=&quot;size16&quot;&gt;async 함수가 호출되면 해당 함수의 완료를 나타내는 새로운 Promise가 만들어진다.&lt;/p&gt;
&lt;p data-end=&quot;17844&quot; data-start=&quot;17788&quot; data-ke-size=&quot;size16&quot;&gt;함수가 다른 Promise를 반환하면 새 Promise는 기존 Promise의 최종 상태를 채택한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;객체 정체성
= 서로 다름

최종 fulfillment 값과 rejection
= 연결됨&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;18029&quot; data-start=&quot;17908&quot; data-ke-size=&quot;size16&quot;&gt;ECMAScript async 함수는 호출마다 새로운 Promise 결과를 생성하며, 반환값이나 예외를 그 Promise의 완료 상태로 연결한다.&lt;/p&gt;
&lt;hr data-end=&quot;18034&quot; data-start=&quot;18031&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;18071&quot; data-start=&quot;18036&quot; data-ke-size=&quot;size26&quot;&gt;10. rejection이 await를 만나면 예외가 된다&lt;/h2&gt;
&lt;p data-end=&quot;18127&quot; data-start=&quot;18073&quot; data-ke-size=&quot;size16&quot;&gt;다음 코드는 rejected Promise를 만들지만 현재 호출 스택에서 예외를 던지지는 않는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;try {
  Promise.reject(new Error(&quot;failure&quot;));
} catch (error) {
  console.log(&quot;실행되지 않는다.&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;18252&quot; data-start=&quot;18234&quot; data-ke-size=&quot;size16&quot;&gt;반면 다음 코드는 오류를 잡는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;try {
  await Promise.reject(new Error(&quot;failure&quot;));
} catch (error) {
  console.log(error.message); // failure
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;18446&quot; data-start=&quot;18378&quot; data-ke-size=&quot;size16&quot;&gt;await은 Promise가 reject되면 중단된 async 실행 컨텍스트를 &lt;b&gt;throw 완료 상태&lt;/b&gt;로 재개한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;Promise rejection
        &amp;darr; await
async 함수 내부의 throw
        &amp;darr;
try/catch로 관찰&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;18574&quot; data-start=&quot;18537&quot; data-ke-size=&quot;size16&quot;&gt;try/catch가 Promise를 직접 이해하는 것은 아니다.&lt;/p&gt;
&lt;p data-end=&quot;18626&quot; data-start=&quot;18576&quot; data-ke-size=&quot;size16&quot;&gt;await이 rejection을 async 함수의 예외 제어 흐름으로 변환하는 것이다.&lt;/p&gt;
&lt;hr data-end=&quot;18631&quot; data-start=&quot;18628&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;18676&quot; data-start=&quot;18633&quot; data-ke-size=&quot;size26&quot;&gt;11. return promise와 return await promise&lt;/h2&gt;
&lt;p data-end=&quot;18701&quot; data-start=&quot;18678&quot; data-ke-size=&quot;size16&quot;&gt;다음 두 함수의 최종 결과는 대체로 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async function loadA() {
  return repository.load();
}

async function loadB() {
  return await repository.load();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;18879&quot; data-start=&quot;18831&quot; data-ke-size=&quot;size16&quot;&gt;하지만 현재 함수의 catch가 rejection을 처리해야 한다면 차이가 생긴다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async function loadUser() {
  try {
    return repository.loadUser();
  } catch (error) {
    throw new Error(&quot;사용자를 불러오지 못했습니다.&quot;, {
      cause: error,
    });
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;19149&quot; data-start=&quot;19058&quot; data-ke-size=&quot;size16&quot;&gt;repository.loadUser()가 Promise를 반환하고 나중에 reject되면, 현재 try 블록은 해당 rejection을 잡지 못할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;19192&quot; data-start=&quot;19151&quot; data-ke-size=&quot;size16&quot;&gt;반환 시점에는 loadUser()의 동기 실행이 이미 끝났기 때문이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async function loadUser() {
  try {
    return await repository.loadUser();
  } catch (error) {
    throw new Error(&quot;사용자를 불러오지 못했습니다.&quot;, {
      cause: error,
    });
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;19418&quot; data-start=&quot;19377&quot; data-ke-size=&quot;size16&quot;&gt;이번에는 rejection이 await 지점에서 throw로 변환된다.&lt;/p&gt;
&lt;p data-end=&quot;19449&quot; data-start=&quot;19420&quot; data-ke-size=&quot;size16&quot;&gt;따라서 현재 catch가 오류를 변환할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;19638&quot; data-start=&quot;19451&quot; data-ke-size=&quot;size16&quot;&gt;과거에는 return await이 추가 마이크로태스크를 만든다는 이유로 피하라는 조언이 있었다. 현재 ESLint는 관련 규칙을 폐기했으며, return await이 추가 마이크로태스크를 만들지 않고 더 나은 비동기 스택 추적을 제공할 수 있다고 설명한다.&lt;/p&gt;
&lt;p data-end=&quot;19654&quot; data-start=&quot;19640&quot; data-ke-size=&quot;size16&quot;&gt;선택 기준은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;현재 함수의 catch 또는 finally가 rejection을 처리해야 함
&amp;rarr; return await

단순히 하위 Promise 결과를 전달
&amp;rarr; return 또는 return await 모두 가능

명확한 async stack trace가 중요
&amp;rarr; return await 고려&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;19828&quot; data-start=&quot;19825&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;19867&quot; data-start=&quot;19830&quot; data-ke-size=&quot;size26&quot;&gt;12. async Promise executor가 위험한 이유&lt;/h2&gt;
&lt;p data-end=&quot;19887&quot; data-start=&quot;19869&quot; data-ke-size=&quot;size16&quot;&gt;다음 코드는 문법적으로 가능하다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;const promise = new Promise(async (resolve, reject) =&amp;gt; {
  const value = await loadValue();

  resolve(value);
});&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;20044&quot; data-start=&quot;20015&quot; data-ke-size=&quot;size16&quot;&gt;하지만 구조적으로는 두 개의 Promise가 생긴다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;1. new Promise가 만든 외부 Promise
2. async executor 호출이 반환한 내부 Promise&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;20162&quot; data-start=&quot;20125&quot; data-ke-size=&quot;size16&quot;&gt;Promise 생성자는 executor의 반환값을 사용하지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;20201&quot; data-start=&quot;20164&quot; data-ke-size=&quot;size16&quot;&gt;따라서 다음 코드는 외부 Promise를 reject시키지 못한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const promise = new Promise(async () =&amp;gt; {
  throw new Error(&quot;failure&quot;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;20304&quot; data-start=&quot;20290&quot; data-ke-size=&quot;size16&quot;&gt;실행 구조는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async executor 실행
&amp;rarr; 별도의 Promise 반환
&amp;rarr; throw로 내부 Promise reject

외부 Promise
&amp;rarr; resolve도 reject도 호출되지 않음
&amp;rarr; 계속 pending&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;20587&quot; data-start=&quot;20432&quot; data-ke-size=&quot;size16&quot;&gt;ESLint도 async executor 내부에서 발생한 오류가 새로 생성한 외부 Promise를 reject시키지 못할 수 있다는 이유로 no-async-promise-executor 규칙을 권장한다.&lt;/p&gt;
&lt;p data-end=&quot;20636&quot; data-start=&quot;20589&quot; data-ke-size=&quot;size16&quot;&gt;이미 Promise를 반환하는 작업에는 new Promise()가 필요하지 않다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async function load() {
  return await loadValue();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;20764&quot; data-start=&quot;20703&quot; data-ke-size=&quot;size16&quot;&gt;new Promise()는 callback 기반 API를 Promise로 변환하는 경계에서 주로 사용한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;moonscript&quot;&gt;&lt;code&gt;function readFile(path) {
  return new Promise((resolve, reject) =&amp;gt; {
    legacyReadFile(path, (error, data) =&amp;gt; {
      if (error) {
        reject(error);
        return;
      }

      resolve(data);
    });
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;20998&quot; data-start=&quot;20995&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;21038&quot; data-start=&quot;21000&quot; data-ke-size=&quot;size26&quot;&gt;13. 처리되지 않은 rejection은 시간의 문제이기도 하다&lt;/h2&gt;
&lt;p data-end=&quot;21067&quot; data-start=&quot;21040&quot; data-ke-size=&quot;size16&quot;&gt;다음 Promise는 생성 즉시 reject된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const promise = Promise.reject(
  new Error(&quot;failure&quot;),
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;21188&quot; data-start=&quot;21139&quot; data-ke-size=&quot;size16&quot;&gt;같은 실행 흐름에서 바로 handler를 등록하면 처리된 rejection으로 관찰된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;mercury&quot;&gt;&lt;code&gt;promise.catch(handleError);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;21297&quot; data-start=&quot;21229&quot; data-ke-size=&quot;size16&quot;&gt;그러나 다음 task에서 늦게 handler를 등록하면 호스트가 먼저 처리되지 않은 rejection으로 보고할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const promise = Promise.reject(
  new Error(&quot;failure&quot;),
);

setTimeout(() =&amp;gt; {
  promise.catch(handleError);
}, 0);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;21488&quot; data-start=&quot;21426&quot; data-ke-size=&quot;size16&quot;&gt;unhandled rejection은 단순히 &amp;ldquo;Promise가 reject되었다&amp;rdquo;는 상태만으로 결정되지 않는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;Promise가 reject되었는가?
적절한 시간 안에 handler가 연결되었는가?
호스트가 언제 이를 검사했는가?&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;21754&quot; data-start=&quot;21568&quot; data-ke-size=&quot;size16&quot;&gt;ECMAScript는 HostPromiseRejectionTracker로 호스트가 rejection 처리 여부를 추적할 수 있게 하며, HTML 명세는 microtask checkpoint와 처리되지 않은 rejected Promise 보고 절차를 연결한다.&lt;/p&gt;
&lt;hr data-end=&quot;21759&quot; data-start=&quot;21756&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;21767&quot; data-start=&quot;21761&quot; data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-end=&quot;21801&quot; data-start=&quot;21769&quot; data-ke-size=&quot;size16&quot;&gt;async/await은 Promise를 없애지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;21840&quot; data-start=&quot;21803&quot; data-ke-size=&quot;size16&quot;&gt;Promise 위에 실행 컨텍스트 중단과 재개라는 구조를 제공한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;async 함수
= 호출 시 새로운 Promise를 생성하는 함수

await
= 현재 async 실행을 중단하고
  Promise 반응을 통해 나중에 재개하는 연산&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;21973&quot; data-start=&quot;21947&quot; data-ke-size=&quot;size16&quot;&gt;이를 이해하면 다음 현상이 자연스럽게 설명된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async 함수가 첫 await 전까지 동기 실행되는 이유
await 42도 실행 순서를 바꾸는 이유
rejection을 try/catch로 잡을 수 있는 이유
Promise 체인 사이에 다른 microtask가 들어오는 이유
microtask를 반복해도 렌더링에 양보되지 않을 수 있는 이유
async executor가 외부 Promise를 pending으로 남길 수 있는 이유&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;22241&quot; data-start=&quot;22200&quot; data-ke-size=&quot;size16&quot;&gt;async/await은 비동기를 동기식 실행으로 바꾸는 문법이 아니다.&lt;/p&gt;
&lt;p data-end=&quot;22283&quot; data-start=&quot;22243&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비동기 연속성을 사람이 읽기 쉬운 선형 코드로 표현하는 문법이다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-end=&quot;22293&quot; data-start=&quot;22285&quot; data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;22626&quot; data-start=&quot;22295&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;22393&quot; data-start=&quot;22295&quot;&gt;ECMA-262, Promise Objects, Async Functions, Await 추상 연산.&lt;/li&gt;
&lt;li data-end=&quot;22478&quot; data-start=&quot;22394&quot;&gt;WHATWG HTML, 이벤트 루프와 microtask checkpoint.&lt;/li&gt;
&lt;li data-end=&quot;22547&quot; data-start=&quot;22479&quot;&gt;ESLint, no-return-await.&lt;/li&gt;
&lt;li data-end=&quot;22626&quot; data-start=&quot;22548&quot;&gt;ESLint, no-async-promise-executor.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Frontend/JavaScript</category>
      <category>Async</category>
      <category>await</category>
      <author>토리나나</author>
      <guid isPermaLink="true">https://khys.tistory.com/113</guid>
      <comments>https://khys.tistory.com/113#entry113comment</comments>
      <pubDate>Tue, 23 Jun 2026 23:05:44 +0900</pubDate>
    </item>
    <item>
      <title>Promise의 의미론: 비동기 작업이 아닌 단일 할당 상태 기계</title>
      <link>https://khys.tistory.com/112</link>
      <description>&lt;blockquote data-end=&quot;139&quot; data-start=&quot;105&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;139&quot; data-start=&quot;107&quot; data-ke-size=&quot;size16&quot;&gt;Promise와 async/await 깊이 이해하기 1/4&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-end=&quot;148&quot; data-start=&quot;141&quot; data-section-id=&quot;1fudykl&quot; data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-end=&quot;187&quot; data-start=&quot;150&quot; data-ke-size=&quot;size16&quot;&gt;Promise는 흔히 &amp;ldquo;나중에 값을 받기 위한 객체&amp;rdquo;라고 설명된다.&lt;/p&gt;
&lt;p data-end=&quot;231&quot; data-start=&quot;189&quot; data-ke-size=&quot;size16&quot;&gt;이 설명은 입문 단계에서는 유용하지만, 다음 질문에는 충분히 답하지 못한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;432&quot; data-start=&quot;233&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;285&quot; data-start=&quot;233&quot; data-section-id=&quot;7e61zq&quot;&gt;resolve()를 호출했는데도 왜 Promise가 여전히 pending일 수 있는가?&lt;/li&gt;
&lt;li data-end=&quot;322&quot; data-start=&quot;286&quot; data-section-id=&quot;popcri&quot;&gt;then()은 왜 항상 새로운 Promise를 반환하는가?&lt;/li&gt;
&lt;li data-end=&quot;361&quot; data-start=&quot;323&quot; data-section-id=&quot;sx6zhd&quot;&gt;Promise 안에 Promise를 반환해도 왜 중첩되지 않는가?&lt;/li&gt;
&lt;li data-end=&quot;391&quot; data-start=&quot;362&quot; data-section-id=&quot;wx56we&quot;&gt;Promise와 thenable은 무엇이 다른가?&lt;/li&gt;
&lt;li data-end=&quot;432&quot; data-start=&quot;392&quot; data-section-id=&quot;17cux19&quot;&gt;이미 만들어진 Promise를 다시 기다리면 작업도 다시 실행되는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;492&quot; data-start=&quot;434&quot; data-ke-size=&quot;size16&quot;&gt;이 질문들에 답하려면 Promise를 단순한 비동기 문법이 아니라 다음 세 가지 관점에서 바라봐야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;Promise = 단일 할당 상태 기계
        + 미래 결과를 관찰하는 프로토콜
        + 후속 계산을 연결하는 구조&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;817&quot; data-start=&quot;580&quot; data-ke-size=&quot;size16&quot;&gt;Promise라는 개념은 JavaScript에서 처음 등장한 것이 아니다. Liskov와 Shrira는 1988년 논문에서 비동기 호출의 결과와 예외를 나중에 안전하게 다룰 수 있는 언어적 구조로 Promise를 연구했다. 현대 JavaScript Promise와 완전히 같은 모델은 아니지만, 미래의 결과를 값처럼 다룬다는 핵심 발상은 같은 계보에 있다.&lt;/p&gt;
&lt;hr data-end=&quot;822&quot; data-start=&quot;819&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;850&quot; data-start=&quot;824&quot; data-section-id=&quot;1m5no9g&quot; data-ke-size=&quot;size26&quot;&gt;1. Promise는 비동기 작업이 아니다&lt;/h2&gt;
&lt;p data-end=&quot;888&quot; data-start=&quot;852&quot; data-ke-size=&quot;size16&quot;&gt;가장 먼저 구분해야 할 것은 &lt;b&gt;작업&lt;/b&gt;과 &lt;b&gt;작업의 결과&lt;/b&gt;다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;비동기 작업
= 네트워크 요청, 타이머, 파일 읽기, 사용자 이벤트 등

Promise
= 해당 작업의 최종 결과를 관찰하기 위한 객체&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;988&quot; data-start=&quot;978&quot; data-ke-size=&quot;size16&quot;&gt;다음 코드를 보자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;const request = fetch(&quot;/api/users&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1080&quot; data-start=&quot;1038&quot; data-ke-size=&quot;size16&quot;&gt;fetch() 호출은 네트워크 작업을 시작하고 Promise를 반환한다.&lt;/p&gt;
&lt;p data-end=&quot;1148&quot; data-start=&quot;1082&quot; data-ke-size=&quot;size16&quot;&gt;반환된 request는 네트워크 작업 자체가 아니다. 요청의 최종 성공 또는 실패를 관찰할 수 있는 핸들에 가깝다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const firstResult = await request;
const secondResult = await request;

console.log(firstResult === secondResult);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1347&quot; data-start=&quot;1276&quot; data-ke-size=&quot;size16&quot;&gt;같은 Promise를 두 번 await한다고 네트워크 요청이 두 번 실행되지는 않는다. 동일한 결과를 두 번 관찰할 뿐이다.&lt;/p&gt;
&lt;p data-end=&quot;1401&quot; data-start=&quot;1349&quot; data-ke-size=&quot;size16&quot;&gt;작업을 다시 실행하려면 Promise가 아니라 &lt;b&gt;Promise를 만드는 함수&lt;/b&gt;가 필요하다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;const request = () =&amp;gt; fetch(&quot;/api/users&quot;);

await request();
await request();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1506&quot; data-start=&quot;1492&quot; data-ke-size=&quot;size16&quot;&gt;두 구조는 의미가 다르다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;Promise&amp;lt;Response&amp;gt;
= 이미 생성된 결과 객체

() =&amp;gt; Promise&amp;lt;Response&amp;gt;
= 새로운 작업을 시작할 수 있는 작업 기술&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1635&quot; data-start=&quot;1603&quot; data-ke-size=&quot;size16&quot;&gt;이 차이는 재시도, 지연 실행, 동시성 제한에서 중요하다.&lt;/p&gt;
&lt;p data-end=&quot;1660&quot; data-start=&quot;1637&quot; data-ke-size=&quot;size16&quot;&gt;다음 코드는 올바른 재시도 구조가 아니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;retry(fetch(&quot;/api/users&quot;));&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1740&quot; data-start=&quot;1701&quot; data-ke-size=&quot;size16&quot;&gt;fetch()는 retry()가 호출되기 전에 이미 시작되었다.&lt;/p&gt;
&lt;p data-end=&quot;1757&quot; data-start=&quot;1742&quot; data-ke-size=&quot;size16&quot;&gt;올바른 구조는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;retry(() =&amp;gt; fetch(&quot;/api/users&quot;));&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1841&quot; data-start=&quot;1804&quot; data-ke-size=&quot;size16&quot;&gt;이제 retry는 실패할 때마다 새로운 작업을 시작할 수 있다.&lt;/p&gt;
&lt;hr data-end=&quot;1846&quot; data-start=&quot;1843&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1882&quot; data-start=&quot;1848&quot; data-section-id=&quot;426e08&quot; data-ke-size=&quot;size26&quot;&gt;2. Promise executor는 동기적으로 실행된다&lt;/h2&gt;
&lt;p data-end=&quot;1916&quot; data-start=&quot;1884&quot; data-ke-size=&quot;size16&quot;&gt;Promise를 생성하면 executor 함수가 전달된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const promise = new Promise((resolve, reject) =&amp;gt; {
  // executor
});&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2031&quot; data-start=&quot;1998&quot; data-ke-size=&quot;size16&quot;&gt;executor 내부 코드는 자동으로 비동기가 되지 않는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;console.log(&quot;A&quot;);

const promise = new Promise((resolve) =&amp;gt; {
  console.log(&quot;B&quot;);
  resolve(1);
});

promise.then(() =&amp;gt; {
  console.log(&quot;D&quot;);
});

console.log(&quot;C&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2223&quot; data-start=&quot;2209&quot; data-ke-size=&quot;size16&quot;&gt;출력 결과는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;A
B
C
D&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2285&quot; data-start=&quot;2245&quot; data-ke-size=&quot;size16&quot;&gt;Promise 생성자는 executor를 생성 즉시 동기적으로 호출한다.&lt;/p&gt;
&lt;p data-end=&quot;2380&quot; data-start=&quot;2287&quot; data-ke-size=&quot;size16&quot;&gt;반면 then()에 등록된 handler는 현재 호출 스택에서 실행되지 않는다. Promise가 이미 fulfilled된 상태여도 후속 반응은 Job으로 예약된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;new Promise(executor)
        &amp;darr;
executor는 즉시 실행

promise.then(handler)
        &amp;darr;
handler는 후속 Job으로 실행&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2658&quot; data-start=&quot;2496&quot; data-ke-size=&quot;size16&quot;&gt;ECMAScript 명세도 Promise 생성 과정에서 executor를 바로 호출하고, Promise의 fulfillment와 rejection 반응은 별도의 Promise Reaction Job으로 실행하도록 정의한다.&lt;/p&gt;
&lt;p data-end=&quot;2679&quot; data-start=&quot;2660&quot; data-ke-size=&quot;size16&quot;&gt;따라서 다음 설명은 정확하지 않다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;new Promise를 사용하면 코드가 비동기로 실행된다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2743&quot; data-start=&quot;2726&quot; data-ke-size=&quot;size16&quot;&gt;더 정확한 설명은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;Promise executor는 동기적으로 실행된다.
Promise의 후속 반응은 비동기 Job으로 실행된다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;2822&quot; data-start=&quot;2819&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;2851&quot; data-start=&quot;2824&quot; data-section-id=&quot;1qd1ywj&quot; data-ke-size=&quot;size26&quot;&gt;3. Promise는 단일 할당 상태 기계다&lt;/h2&gt;
&lt;p data-end=&quot;2880&quot; data-start=&quot;2853&quot; data-ke-size=&quot;size16&quot;&gt;Promise를 추상적인 상태 기계로 표현해보자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;pf&quot;&gt;&lt;code&gt;P = ⟨state, result, fulfillReactions, rejectReactions, handled⟩&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2973&quot; data-start=&quot;2958&quot; data-ke-size=&quot;size16&quot;&gt;각 요소는 다음을 의미한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;state
= pending | fulfilled | rejected

result
= fulfillment value 또는 rejection reason

fulfillReactions
= 성공 시 실행할 후속 반응 목록

rejectReactions
= 실패 시 실행할 후속 반응 목록

handled
= rejection handler가 연결되었는지 나타내는 정보&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3215&quot; data-start=&quot;3194&quot; data-ke-size=&quot;size16&quot;&gt;상태 전이는 다음 두 방향만 가능하다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;pending ── fulfill(value) ──▶ fulfilled(value)

pending ── reject(reason) ──▶ rejected(reason)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3352&quot; data-start=&quot;3324&quot; data-ke-size=&quot;size16&quot;&gt;한 번 완료된 Promise는 다시 바뀌지 않는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;fulfilled ──X──▶ rejected
rejected  ──X──▶ fulfilled&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3441&quot; data-start=&quot;3419&quot; data-ke-size=&quot;size16&quot;&gt;다음 코드에서 첫 번째 호출만 유효하다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const promise = new Promise((resolve, reject) =&amp;gt; {
  resolve(1);
  reject(new Error(&quot;failure&quot;));
  resolve(2);
});

console.log(await promise); // 1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3629&quot; data-start=&quot;3603&quot; data-ke-size=&quot;size16&quot;&gt;Promise는 최초의 유효한 결정만 채택한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;resolve(1)
&amp;rarr; Promise의 운명 결정

reject(...)
&amp;rarr; 무시

resolve(2)
&amp;rarr; 무시&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3779&quot; data-start=&quot;3706&quot; data-ke-size=&quot;size16&quot;&gt;이 특성 때문에 Promise를 &lt;b&gt;single-assignment cell&lt;/b&gt;, 즉 단 한 번만 값이 결정되는 셀로 볼 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;3926&quot; data-start=&quot;3781&quot; data-ke-size=&quot;size16&quot;&gt;ECMAScript Promise는 내부적으로 상태, 결과, fulfillment 반응 목록, rejection 반응 목록, 처리 여부 등을 관리하며, 완료된 이후 다시 상태가 바뀌지 않는다.&lt;/p&gt;
&lt;hr data-end=&quot;3931&quot; data-start=&quot;3928&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;3982&quot; data-start=&quot;3933&quot; data-section-id=&quot;1njl02v&quot; data-ke-size=&quot;size26&quot;&gt;4. fulfilled, rejected, settled, resolved는 다르다&lt;/h2&gt;
&lt;p data-end=&quot;4026&quot; data-start=&quot;3984&quot; data-ke-size=&quot;size16&quot;&gt;Promise를 이해할 때 가장 혼동하기 쉬운 개념이 resolved다.&lt;/p&gt;
&lt;p data-end=&quot;4056&quot; data-start=&quot;4028&quot; data-ke-size=&quot;size16&quot;&gt;먼저 settled는 다음 두 상태를 가리킨다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;fulfilled 또는 rejected&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4116&quot; data-start=&quot;4092&quot; data-ke-size=&quot;size16&quot;&gt;즉, 더 이상 pending이 아닌 상태다.&lt;/p&gt;
&lt;p data-end=&quot;4138&quot; data-start=&quot;4118&quot; data-ke-size=&quot;size16&quot;&gt;반면 resolved는 더 넓다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. fulfilled되었거나
2. rejected되었거나
3. 다른 Promise의 최종 상태를 따르도록 운명이 고정된 상태&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4266&quot; data-start=&quot;4223&quot; data-ke-size=&quot;size16&quot;&gt;따라서 Promise는 &lt;b&gt;resolved이면서 pending일 수 있다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-end=&quot;4278&quot; data-start=&quot;4268&quot; data-ke-size=&quot;size16&quot;&gt;다음 예제를 보자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;let resolveInner;

const inner = new Promise((resolve) =&amp;gt; {
  resolveInner = resolve;
});

let resolveOuter;

const outer = new Promise((resolve) =&amp;gt; {
  resolveOuter = resolve;
});

resolveOuter(inner);
resolveOuter(100);

outer.then(console.log);

setTimeout(() =&amp;gt; {
  resolveInner(&quot;done&quot;);
}, 1000);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4609&quot; data-start=&quot;4593&quot; data-ke-size=&quot;size16&quot;&gt;출력은 1초 후 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;done&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4694&quot; data-start=&quot;4628&quot; data-ke-size=&quot;size16&quot;&gt;resolveOuter(inner)가 호출되는 순간 outer는 inner의 최종 상태를 따르도록 결정된다.&lt;/p&gt;
&lt;p data-end=&quot;4722&quot; data-start=&quot;4696&quot; data-ke-size=&quot;size16&quot;&gt;그러나 inner는 아직 pending이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;outer
= resolved
= pending&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4788&quot; data-start=&quot;4763&quot; data-ke-size=&quot;size16&quot;&gt;이후 호출한 다음 코드는 영향을 주지 못한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;resolveOuter(100);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4861&quot; data-start=&quot;4820&quot; data-ke-size=&quot;size16&quot;&gt;이미 outer의 운명이 inner를 따르도록 결정되었기 때문이다.&lt;/p&gt;
&lt;p data-end=&quot;4882&quot; data-start=&quot;4863&quot; data-ke-size=&quot;size16&quot;&gt;시간 흐름을 표현하면 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;t0
outer: pending, unresolved
inner: pending

t1
resolveOuter(inner)

outer: pending, resolved
inner: pending

t2
resolveOuter(100)

이미 resolved이므로 무시

t3
resolveInner(&quot;done&quot;)

inner: fulfilled(&quot;done&quot;)
outer: fulfilled(&quot;done&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5281&quot; data-start=&quot;5123&quot; data-ke-size=&quot;size16&quot;&gt;ECMAScript 명세 역시 resolved Promise가 pending 상태일 수 있다고 명시한다. 다른 Promise의 상태를 따르도록 고정된 Promise는 아직 최종 값이 없더라도 이미 resolved다.&lt;/p&gt;
&lt;hr data-end=&quot;5286&quot; data-start=&quot;5283&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;5318&quot; data-start=&quot;5288&quot; data-section-id=&quot;vygak9&quot; data-ke-size=&quot;size26&quot;&gt;5. resolve는 값을 저장하는 함수가 아니다&lt;/h2&gt;
&lt;p data-end=&quot;5371&quot; data-start=&quot;5320&quot; data-ke-size=&quot;size16&quot;&gt;resolve(value)는 Promise 내부에 값을 단순히 저장하는 함수처럼 보인다.&lt;/p&gt;
&lt;p data-end=&quot;5392&quot; data-start=&quot;5373&quot; data-ke-size=&quot;size16&quot;&gt;하지만 실제 의미는 다음에 가깝다.&lt;/p&gt;
&lt;blockquote data-end=&quot;5472&quot; data-start=&quot;5394&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;5472&quot; data-start=&quot;5396&quot; data-ke-size=&quot;size16&quot;&gt;주어진 값이 일반 값인지, Promise인지, thenable인지 검사한 뒤 그 값의 최종 상태를 현재 Promise가 채택하도록 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;5508&quot; data-start=&quot;5474&quot; data-ke-size=&quot;size16&quot;&gt;개념적인 resolution algorithm은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;resolve(x)

1. 이미 resolve 또는 reject가 호출되었다면 종료한다.
2. x가 현재 Promise 자신이라면 TypeError로 reject한다.
3. x가 객체나 함수가 아니라면 x로 fulfill한다.
4. x.then을 읽는다.
5. then을 읽는 과정에서 예외가 발생하면 reject한다.
6. then이 함수가 아니라면 x 자체로 fulfill한다.
7. then이 함수라면 별도의 Job에서 호출한다.
8. then이 전달한 결과를 같은 규칙으로 다시 해석한다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5836&quot; data-start=&quot;5800&quot; data-ke-size=&quot;size16&quot;&gt;이 과정 때문에 Promise는 중첩 결과를 자동으로 평탄화한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const promise = Promise.resolve(
  Promise.resolve(
    Promise.resolve(42),
  ),
);

console.log(await promise); // 42&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5989&quot; data-start=&quot;5969&quot; data-ke-size=&quot;size16&quot;&gt;최종 타입은 개념적으로 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;Promise&amp;lt;number&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6040&quot; data-start=&quot;6019&quot; data-ke-size=&quot;size16&quot;&gt;다음처럼 끝없이 중첩된 구조가 아니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;Promise&amp;lt;Promise&amp;lt;Promise&amp;lt;number&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;6091&quot; data-start=&quot;6088&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;6120&quot; data-start=&quot;6093&quot; data-section-id=&quot;65ujh9&quot; data-ke-size=&quot;size26&quot;&gt;6. thenable assimilation&lt;/h2&gt;
&lt;p data-end=&quot;6186&quot; data-start=&quot;6122&quot; data-ke-size=&quot;size16&quot;&gt;Promise와 비슷하게 동작하지만 실제 Promise 인스턴스가 아닌 객체를 &lt;b&gt;thenable&lt;/b&gt;이라고 부른다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;const thenable = {
  then(resolve) {
    resolve(42);
  },
};

const value = await Promise.resolve(thenable);

console.log(value); // 42&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6390&quot; data-start=&quot;6336&quot; data-ke-size=&quot;size16&quot;&gt;Promise.resolve()는 단순히 객체를 fulfillment 값으로 저장하지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;6442&quot; data-start=&quot;6392&quot; data-ke-size=&quot;size16&quot;&gt;객체의 then 프로퍼티가 callable이면 해당 객체의 최종 상태를 채택하려 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;객체에 호출 가능한 then이 존재한다
&amp;rarr; Promise처럼 동작할 가능성이 있는 객체로 취급&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6555&quot; data-start=&quot;6509&quot; data-ke-size=&quot;size16&quot;&gt;이 구조 덕분에 서로 다른 Promise 구현과 라이브러리가 상호 운용될 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;6609&quot; data-start=&quot;6557&quot; data-ke-size=&quot;size16&quot;&gt;그러나 thenable assimilation은 외부 사용자 코드를 실행하는 지점이기도 하다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const thenable = {
  get then() {
    console.log(&quot;then getter&quot;);

    return (resolve) =&amp;gt; {
      console.log(&quot;then called&quot;);
      resolve(1);
    };
  },
};

console.log(&quot;A&quot;);

const promise = Promise.resolve(thenable);

console.log(&quot;B&quot;);

await promise;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6894&quot; data-start=&quot;6880&quot; data-ke-size=&quot;size16&quot;&gt;실행 순서는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;A
then getter
B
then called&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6969&quot; data-start=&quot;6936&quot; data-ke-size=&quot;size16&quot;&gt;then 프로퍼티를 읽는 작업은 현재 흐름에서 일어난다.&lt;/p&gt;
&lt;p data-end=&quot;7018&quot; data-start=&quot;6971&quot; data-ke-size=&quot;size16&quot;&gt;읽어온 then 함수를 호출하는 작업은 별도의 Promise Job으로 예약된다.&lt;/p&gt;
&lt;p data-end=&quot;7046&quot; data-start=&quot;7020&quot; data-ke-size=&quot;size16&quot;&gt;또한 getter 자체가 예외를 던질 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const thenable = {
  get then() {
    throw new Error(&quot;getter failed&quot;);
  },
};

const promise = Promise.resolve(thenable);

promise.catch(console.error);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7258&quot; data-start=&quot;7214&quot; data-ke-size=&quot;size16&quot;&gt;이 경우 반환된 Promise는 getter에서 발생한 오류로 reject된다.&lt;/p&gt;
&lt;p data-end=&quot;7442&quot; data-start=&quot;7260&quot; data-ke-size=&quot;size16&quot;&gt;ECMAScript의 Promise resolution procedure는 객체의 then을 읽고, callable이면 별도의 Thenable Job을 통해 호출하며, 자기 자신으로 resolve하려는 경우에는 TypeError로 거부하도록 정의한다.&lt;/p&gt;
&lt;hr data-end=&quot;7447&quot; data-start=&quot;7444&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;7477&quot; data-start=&quot;7449&quot; data-section-id=&quot;1r2mtpy&quot; data-ke-size=&quot;size26&quot;&gt;7. 악의적이거나 잘못 구현된 thenable&lt;/h2&gt;
&lt;p data-end=&quot;7521&quot; data-start=&quot;7479&quot; data-ke-size=&quot;size16&quot;&gt;thenable은 여러 번 resolve와 reject를 호출할 수도 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const hostileThenable = {
  then(resolve, reject) {
    resolve(1);
    reject(new Error(&quot;late rejection&quot;));
    resolve(2);

    throw new Error(&quot;late throw&quot;);
  },
};

const value = await Promise.resolve(hostileThenable);

console.log(value); // 1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7797&quot; data-start=&quot;7784&quot; data-ke-size=&quot;size16&quot;&gt;최종 결과는 1이다.&lt;/p&gt;
&lt;p data-end=&quot;7848&quot; data-start=&quot;7799&quot; data-ke-size=&quot;size16&quot;&gt;첫 번째 호출이 Promise의 운명을 결정한 이후 나머지 호출은 상태를 바꾸지 못한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;resolve(1)
&amp;rarr; 최종 운명 결정

reject(...)
&amp;rarr; 무시

resolve(2)
&amp;rarr; 무시

throw
&amp;rarr; 이미 결정되었으므로 최종 결과를 바꾸지 못함&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8002&quot; data-start=&quot;7953&quot; data-ke-size=&quot;size16&quot;&gt;이 규칙은 잘못 구현된 thenable이 Promise를 여러 번 결정하는 것을 막는다.&lt;/p&gt;
&lt;p data-end=&quot;8033&quot; data-start=&quot;8004&quot; data-ke-size=&quot;size16&quot;&gt;그러나 실행된 부수 효과까지 되돌려주는 것은 아니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;const thenable = {
  then(resolve) {
    chargePayment();
    resolve(&quot;success&quot;);
    sendEmail();
  },
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8215&quot; data-start=&quot;8153&quot; data-ke-size=&quot;size16&quot;&gt;Promise는 한 번만 완료되지만 chargePayment()와 sendEmail()은 모두 실행된다.&lt;/p&gt;
&lt;p data-end=&quot;8250&quot; data-start=&quot;8217&quot; data-ke-size=&quot;size16&quot;&gt;Promise의 단일 할당 규칙은 결과의 일관성을 보호한다.&lt;/p&gt;
&lt;p data-end=&quot;8268&quot; data-start=&quot;8252&quot; data-ke-size=&quot;size16&quot;&gt;다음 항목은 보호하지 않는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;부수 효과의 원자성
트랜잭션
롤백
중복 실행 방지&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;8313&quot; data-start=&quot;8310&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;8342&quot; data-start=&quot;8315&quot; data-section-id=&quot;uec5dc&quot; data-ke-size=&quot;size26&quot;&gt;8. then은 결과를 꺼내는 함수가 아니다&lt;/h2&gt;
&lt;p data-end=&quot;8354&quot; data-start=&quot;8344&quot; data-ke-size=&quot;size16&quot;&gt;다음 코드를 보자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;const nextPromise = promise.then(
  onFulfilled,
  onRejected,
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8464&quot; data-start=&quot;8433&quot; data-ke-size=&quot;size16&quot;&gt;then()은 원본 Promise를 변경하지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;8512&quot; data-start=&quot;8466&quot; data-ke-size=&quot;size16&quot;&gt;원본 Promise에 후속 반응을 등록하고 &lt;b&gt;새로운 Promise를 반환&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-end=&quot;8544&quot; data-start=&quot;8514&quot; data-ke-size=&quot;size16&quot;&gt;타입 수준에서 단순화하면 다음과 같이 표현할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;then:
Promise&amp;lt;A&amp;gt;
&amp;times; (A &amp;rarr; B | PromiseLike&amp;lt;B&amp;gt;)
&amp;times; (unknown &amp;rarr; B | PromiseLike&amp;lt;B&amp;gt;)
&amp;rarr; Promise&amp;lt;B&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8689&quot; data-start=&quot;8648&quot; data-ke-size=&quot;size16&quot;&gt;새 Promise의 결과는 handler가 무엇을 하느냐에 따라 결정된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;handler가 일반 값 y를 반환
&amp;rarr; 새 Promise는 y로 fulfill

handler가 Promise를 반환
&amp;rarr; 새 Promise는 해당 Promise의 상태를 채택

handler가 예외를 던짐
&amp;rarr; 새 Promise는 해당 오류로 reject&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8852&quot; data-start=&quot;8845&quot; data-ke-size=&quot;size16&quot;&gt;예제를 보자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;const result = Promise.resolve(1)
  .then((value) =&amp;gt; value + 1)
  .then((value) =&amp;gt; Promise.resolve(value + 1))
  .then((value) =&amp;gt; {
    if (value === 3) {
      throw new Error(&quot;stopped&quot;);
    }

    return value;
  });

result.catch((error) =&amp;gt; {
  console.log(error.message); // stopped
});&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9188&quot; data-start=&quot;9157&quot; data-ke-size=&quot;size16&quot;&gt;각 then()은 하나의 새로운 계산 단계를 만든다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Promise 1
  &amp;darr; handler
Promise 2
  &amp;darr; handler
Promise 3
  &amp;darr; handler
Promise 4&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9348&quot; data-start=&quot;9278&quot; data-ke-size=&quot;size16&quot;&gt;Promise 체인은 값이 담긴 상자를 순서대로 여는 구조라기보다, &lt;b&gt;결과와 오류가 흐르는 비동기 계산 그래프&lt;/b&gt;에 가깝다.&lt;/p&gt;
&lt;hr data-end=&quot;9353&quot; data-start=&quot;9350&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;9381&quot; data-start=&quot;9355&quot; data-section-id=&quot;13gk4lt&quot; data-ke-size=&quot;size26&quot;&gt;9. handler가 없을 때의 전파 규칙&lt;/h2&gt;
&lt;p data-end=&quot;9415&quot; data-start=&quot;9383&quot; data-ke-size=&quot;size16&quot;&gt;다음 코드에는 fulfillment handler가 없다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;const nextPromise = promise.then();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9510&quot; data-start=&quot;9464&quot; data-ke-size=&quot;size16&quot;&gt;원본 Promise가 fulfill되면 값이 다음 Promise로 그대로 전달된다.&lt;/p&gt;
&lt;p data-end=&quot;9535&quot; data-start=&quot;9512&quot; data-ke-size=&quot;size16&quot;&gt;개념적으로 다음 함수가 들어간 것과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ocaml&quot;&gt;&lt;code&gt;(value) =&amp;gt; value&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9609&quot; data-start=&quot;9565&quot; data-ke-size=&quot;size16&quot;&gt;rejection handler가 없으면 오류도 다음 Promise로 전달된다.&lt;/p&gt;
&lt;p data-end=&quot;9625&quot; data-start=&quot;9611&quot; data-ke-size=&quot;size16&quot;&gt;개념적으로는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;(error) =&amp;gt; {
  throw error;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9704&quot; data-start=&quot;9668&quot; data-ke-size=&quot;size16&quot;&gt;따라서 다음 체인의 오류는 마지막 catch()까지 전파된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;fetchData()
  .then(parseData)
  .then(normalizeData)
  .then(renderData)
  .catch(handleError);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9861&quot; data-start=&quot;9814&quot; data-ke-size=&quot;size16&quot;&gt;어느 단계에서든 오류가 발생하면 handler가 없는 구간을 통과해 아래로 전달된다.&lt;/p&gt;
&lt;hr data-end=&quot;9866&quot; data-start=&quot;9863&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;9896&quot; data-start=&quot;9868&quot; data-section-id=&quot;1atdmi6&quot; data-ke-size=&quot;size26&quot;&gt;10. then의 두 번째 인자가 놓치는 오류&lt;/h2&gt;
&lt;p data-end=&quot;9917&quot; data-start=&quot;9898&quot; data-ke-size=&quot;size16&quot;&gt;다음 코드는 미묘한 차이를 가진다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;moonscript&quot;&gt;&lt;code&gt;sourcePromise.then(
  (value) =&amp;gt; parse(value),
  (error) =&amp;gt; recover(error),
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10067&quot; data-start=&quot;10009&quot; data-ke-size=&quot;size16&quot;&gt;두 번째 인자인 recover는 sourcePromise 자체가 reject된 경우에만 실행된다.&lt;/p&gt;
&lt;p data-end=&quot;10113&quot; data-start=&quot;10069&quot; data-ke-size=&quot;size16&quot;&gt;첫 번째 handler인 parse() 내부에서 발생한 오류는 잡지 못한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;Promise.resolve(&quot;{ invalid json&quot;)
  .then(
    (value) =&amp;gt; JSON.parse(value),
    (error) =&amp;gt; {
      console.log(&quot;여기서는 잡히지 않는다.&quot;);
    },
  )
  .catch((error) =&amp;gt; {
    console.log(&quot;여기에서 잡힌다.&quot;);
  });&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10372&quot; data-start=&quot;10325&quot; data-ke-size=&quot;size16&quot;&gt;이유는 두 handler가 같은 원본 Promise에 연결된 형제 반응이기 때문이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;pf&quot;&gt;&lt;code&gt;                 ┌─ onFulfilled
sourcePromise ───┤
                 └─ onRejected&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10510&quot; data-start=&quot;10468&quot; data-ke-size=&quot;size16&quot;&gt;onFulfilled가 던진 오류는 원본 Promise의 실패가 아니다.&lt;/p&gt;
&lt;p data-end=&quot;10551&quot; data-start=&quot;10512&quot; data-ke-size=&quot;size16&quot;&gt;onFulfilled가 생성한 &lt;b&gt;다음 Promise의 실패&lt;/b&gt;다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;sourcePromise fulfilled
        &amp;darr;
onFulfilled 실행
        &amp;darr; throw
nextPromise rejected&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10700&quot; data-start=&quot;10651&quot; data-ke-size=&quot;size16&quot;&gt;따라서 성공 handler 내부 오류까지 잡으려면 하류에 catch()를 둬야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;moonscript&quot;&gt;&lt;code&gt;sourcePromise
  .then((value) =&amp;gt; parse(value))
  .catch((error) =&amp;gt; recover(error));&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;10800&quot; data-start=&quot;10797&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;10828&quot; data-start=&quot;10802&quot; data-section-id=&quot;4pvmst&quot; data-ke-size=&quot;size26&quot;&gt;11. Promise를 이해하는 핵심 모델&lt;/h2&gt;
&lt;p data-end=&quot;10855&quot; data-start=&quot;10830&quot; data-ke-size=&quot;size16&quot;&gt;Promise는 다음과 같이 이해할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;Promise는 작업이 아니다.
Promise는 작업 결과를 관찰하는 객체다.

Promise는 단 한 번 결정된다.
완료된 이후 상태는 바뀌지 않는다.

resolve는 값을 저장하지 않는다.
값 또는 thenable의 최종 운명을 채택한다.

then은 원본 Promise를 수정하지 않는다.
후속 계산을 나타내는 새로운 Promise를 만든다.

Promise 체인은 값의 배열이 아니다.
결과와 오류가 흐르는 방향성 계산 그래프다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;11184&quot; data-start=&quot;11115&quot; data-ke-size=&quot;size16&quot;&gt;이 모델을 이해하면 이후에 다룰 async/await, 마이크로태스크, 동시성 조합자, 취소 구조도 자연스럽게 연결된다.&lt;/p&gt;
&lt;hr data-end=&quot;11189&quot; data-start=&quot;11186&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;11197&quot; data-start=&quot;11191&quot; data-section-id=&quot;1h9nj85&quot; data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-end=&quot;11232&quot; data-start=&quot;11199&quot; data-ke-size=&quot;size16&quot;&gt;Promise는 단순히 &amp;ldquo;나중에 값을 주는 객체&amp;rdquo;가 아니다.&lt;/p&gt;
&lt;p data-end=&quot;11272&quot; data-start=&quot;11234&quot; data-ke-size=&quot;size16&quot;&gt;더 정확히 말하면 Promise는 다음을 하나의 언어 구조로 만든다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;미래의 결과
결과의 성공과 실패
결과에 연결된 후속 계산
다른 비동기 결과와의 결합&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;11381&quot; data-start=&quot;11333&quot; data-ke-size=&quot;size16&quot;&gt;Promise를 단일 할당 상태 기계로 이해하면 다음 현상들이 하나의 원리로 설명된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;resolve를 여러 번 호출해도 첫 호출만 유효한 이유
다른 pending Promise로 resolve할 수 있는 이유
thenable이 자동으로 흡수되는 이유
중첩 Promise가 평탄화되는 이유
then이 항상 새 Promise를 반환하는 이유
오류가 체인 아래로 전파되는 이유&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;11582&quot; data-start=&quot;11555&quot; data-ke-size=&quot;size16&quot;&gt;Promise는 비동기를 실행하는 엔진이 아니다.&lt;/p&gt;
&lt;p data-end=&quot;11641&quot; data-start=&quot;11584&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Promise는 비동기 계산의 결과와 후속 관계를 프로그램 안에서 값으로 표현하는 프로토콜이다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-end=&quot;11651&quot; data-start=&quot;11643&quot; data-section-id=&quot;3c0sv5&quot; data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;11904&quot; data-start=&quot;11653&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;11806&quot; data-start=&quot;11653&quot; data-section-id=&quot;31ywtc&quot;&gt;Liskov, Shrira, Promises: Linguistic Support for Efficient Asynchronous Procedure Calls in Distributed Systems.&lt;/li&gt;
&lt;li data-end=&quot;11904&quot; data-start=&quot;11807&quot; data-section-id=&quot;16zf5bg&quot;&gt;ECMA-262, Promise Objects 및 Promise Resolution Procedure.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Frontend/JavaScript</category>
      <category>Promise</category>
      <author>토리나나</author>
      <guid isPermaLink="true">https://khys.tistory.com/112</guid>
      <comments>https://khys.tistory.com/112#entry112comment</comments>
      <pubDate>Mon, 22 Jun 2026 22:35:03 +0900</pubDate>
    </item>
    <item>
      <title>JavaScript 클로저 완전 정복: 함수는 어떻게 자신이 태어난 환경을 기억하는가</title>
      <link>https://khys.tistory.com/111</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;javascript_closure_fun_illustration.png&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lOQkg/dJMcafG4OGw/Z2IvSrD0zWImVnTkWkWiX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lOQkg/dJMcafG4OGw/Z2IvSrD0zWImVnTkWkWiX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lOQkg/dJMcafG4OGw/Z2IvSrD0zWImVnTkWkWiX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlOQkg%2FdJMcafG4OGw%2FZ2IvSrD0zWImVnTkWkWiX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1600&quot; height=&quot;840&quot; data-filename=&quot;javascript_closure_fun_illustration.png&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발자 면접에서 자주 나오는 JavaScript 질문 중 하나가 바로 &lt;b&gt;클로저(Closure)&lt;/b&gt; 다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 개발자가 클로저를 &amp;ldquo;함수 안의 함수&amp;rdquo; 또는 &amp;ldquo;외부 변수에 접근하는 내부 함수&amp;rdquo; 정도로 기억한다. 하지만 이 설명은 절반만 맞다. 클로저를 제대로 이해하려면 단순히 코드 패턴을 외우는 것이 아니라, JavaScript가 &lt;b&gt;스코프를 어떻게 결정하는지&lt;/b&gt;, 함수가 &lt;b&gt;어떤 환경을 기억하는지&lt;/b&gt;, 그리고 그 특성이 실무에서 &lt;b&gt;상태 관리, 이벤트 핸들러, 비동기 처리, React Hooks, 메모리 관리&lt;/b&gt;와 어떻게 연결되는지까지 이해해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 클로저를 기초부터 깊이 있게 정리해본다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 클로저를 한 문장으로 정의하면&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저는 &lt;b&gt;함수와 그 함수가 선언될 당시의 렉시컬 환경이 함께 묶인 구조&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 더 쉽게 말하면 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저는 함수가 자신이 만들어진 위치의 변수와 스코프를 기억하고, 나중에 다른 위치에서 실행되더라도 그 변수에 접근할 수 있게 해주는 JavaScript의 특성이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 기본적인 예시를 보자.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function createCounter() {
  let count = 0;

  return function increment() {
    count += 1;
    return count;
  };
}

const counter = createCounter();

console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;createCounter 함수는 이미 실행이 끝났다. 일반적으로 함수 실행이 끝나면 내부 변수도 사라질 것처럼 느껴진다. 그런데 counter()를 호출할 때마다 count 값은 계속 유지된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 그럴까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;increment 함수가 만들어질 때, 자신이 선언된 환경인 createCounter의 렉시컬 환경을 기억했기 때문이다. 그래서 createCounter의 실행은 끝났지만, increment가 여전히 count에 접근할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 클로저다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 클로저는 &amp;ldquo;함수 안의 함수&amp;rdquo;가 아니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저를 처음 배울 때 흔히 이렇게 외운다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 안에 함수가 있고, 내부 함수가 외부 함수의 변수에 접근하면 클로저다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입문 단계에서는 괜찮은 설명이다. 하지만 정확한 설명은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저의 핵심은 &amp;ldquo;중첩 함수&amp;rdquo; 자체가 아니라, &lt;b&gt;함수가 선언될 때의 외부 렉시컬 환경을 기억한다는 점&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 예제를 보자.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;let name = &quot;Hyun&quot;;

function sayName() {
  console.log(name);
}

name = &quot;Kwak&quot;;

sayName(); // Kwak
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sayName은 전역 스코프에서 선언되었고, 전역 변수 name에 접근한다. 이 함수도 자신이 선언된 렉시컬 환경을 참조한다. 넓은 의미에서 JavaScript의 함수는 모두 자신이 생성된 환경을 기억한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 보통 면접이나 실무에서 &amp;ldquo;클로저&amp;rdquo;라고 말할 때는 다음과 같은 상황을 주로 가리킨다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function outer() {
  const message = &quot;Hello Closure&quot;;

  return function inner() {
    console.log(message);
  };
}

const fn = outer();
fn(); // Hello Closure
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;외부 함수의 실행이 끝난 뒤에도 내부 함수가 외부 함수의 변수에 접근하는 상황&lt;/b&gt;에서 클로저의 특징이 가장 잘 드러난다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 렉시컬 스코프를 먼저 이해해야 한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저를 이해하려면 먼저 &lt;b&gt;렉시컬 스코프(Lexical Scope)&lt;/b&gt; 를 알아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렉시컬 스코프란, 변수의 유효 범위가 &lt;b&gt;함수를 어디서 호출했는지&lt;/b&gt;가 아니라 &lt;b&gt;어디서 선언했는지&lt;/b&gt;에 따라 결정되는 규칙이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코드를 보자.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const value = &quot;global&quot;;

function outer() {
  const value = &quot;outer&quot;;

  function inner() {
    console.log(value);
  }

  return inner;
}

const fn = outer();

function run() {
  const value = &quot;run&quot;;
  fn();
}

run(); // outer
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fn()은 run 함수 안에서 호출되었다. run 함수 안에도 value가 있다. 그런데 출력 결과는 &quot;run&quot;이 아니라 &quot;outer&quot;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐하면 inner 함수는 run 안에서 선언된 것이 아니라 outer 안에서 선언되었기 때문이다. JavaScript는 함수가 실행되는 위치가 아니라 &lt;b&gt;선언된 위치를 기준으로 상위 스코프를 결정&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 렉시컬 스코프다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저는 이 렉시컬 스코프 규칙 위에서 동작한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 클로저의 핵심: 값이 아니라 &amp;ldquo;환경&amp;rdquo;을 기억한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저를 이해할 때 중요한 포인트가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저는 단순히 외부 변수의 &amp;ldquo;값&amp;rdquo;을 복사해서 저장하는 것이 아니다. 함수가 선언될 당시의 &lt;b&gt;변수 바인딩이 들어 있는 환경&lt;/b&gt;을 참조한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제를 보자.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;let count = 0;

function logCount() {
  console.log(count);
}

count = 10;

logCount(); // 10
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 함수가 변수를 만들 당시의 값을 복사했다면 0이 출력되어야 할 것 같다. 하지만 실제로는 10이 출력된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 logCount가 count라는 값을 복사한 것이 아니라, count라는 변수 바인딩에 접근하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 예시도 보자.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;function createCounter() {
  let count = 0;

  return {
    increase() {
      count += 1;
    },
    decrease() {
      count -= 1;
    },
    getCount() {
      return count;
    },
  };
}

const counter = createCounter();

counter.increase();
counter.increase();
counter.decrease();

console.log(counter.getCount()); // 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;increase, decrease, getCount는 모두 같은 count를 공유한다. 각 함수가 count 값을 따로 복사해서 가지고 있는 것이 아니라, 같은 렉시컬 환경 안의 count 바인딩을 참조하고 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 특성 덕분에 클로저는 상태를 은닉하고 공유하는 강력한 도구가 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 클로저가 만들어지는 과정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코드를 기준으로 클로저가 어떻게 만들어지는지 단계별로 보자.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;function createUser(name) {
  const createdAt = new Date();

  return function getUserInfo() {
    return {
      name,
      createdAt,
    };
  };
}

const getUserInfo = createUser(&quot;Hyun&quot;);

console.log(getUserInfo());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 흐름은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, createUser(&quot;Hyun&quot;)이 호출된다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;name = &quot;Hyun&quot;
createdAt = new Date()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘째, getUserInfo 함수가 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수는 자신이 선언된 위치의 렉시컬 환경을 기억한다. 즉, name과 createdAt에 접근할 수 있는 환경을 참조한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셋째, createUser는 getUserInfo 함수를 반환하고 종료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 지역 변수라면 createUser 실행이 끝난 뒤 사라져도 이상하지 않다. 하지만 반환된 getUserInfo 함수가 여전히 그 환경을 참조하고 있기 때문에, 해당 환경은 메모리에서 해제되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;넷째, 나중에 getUserInfo()를 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수는 자신이 기억하고 있던 환경을 통해 name과 createdAt에 접근한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 클로저는 다음 구조로 이해할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;getUserInfo 함수
  └── 자신이 선언된 렉시컬 환경 참조
        ├── name: &quot;Hyun&quot;
        └── createdAt: Date 객체&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 클로저의 대표적인 활용 1: private state 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaScript에서 클로저는 private한 상태를 만드는 데 자주 사용된다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;function createBankAccount(initialBalance) {
  let balance = initialBalance;

  return {
    deposit(amount) {
      if (amount &amp;lt;= 0) {
        throw new Error(&quot;입금액은 0보다 커야 합니다.&quot;);
      }

      balance += amount;
      return balance;
    },

    withdraw(amount) {
      if (amount &amp;gt; balance) {
        throw new Error(&quot;잔액이 부족합니다.&quot;);
      }

      balance -= amount;
      return balance;
    },

    getBalance() {
      return balance;
    },
  };
}

const account = createBankAccount(10000);

account.deposit(5000);
account.withdraw(3000);

console.log(account.getBalance()); // 12000
console.log(account.balance); // undefined&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;balance는 외부에서 직접 접근할 수 없다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;account.balance = 100000000;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해도 내부의 balance 값은 바뀌지 않는다. 외부에서 만든 account.balance는 단지 새로운 프로퍼티일 뿐이고, 클로저 내부의 balance와는 별개다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 패턴은 클래스의 private 필드가 널리 쓰이기 전부터 JavaScript에서 캡슐화를 구현하는 대표적인 방식이었다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 클로저의 대표적인 활용 2: 함수 팩토리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저는 특정 설정을 기억하는 함수를 만들 때 유용하다.&lt;/p&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;function createMultiplier(multiplier) {
  return function multiply(value) {
    return value * multiplier;
  };
}

const double = createMultiplier(2);
const triple = createMultiplier(3);

console.log(double(10)); // 20
console.log(triple(10)); // 30
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;double은 multiplier = 2인 환경을 기억하고, triple은 multiplier = 3인 환경을 기억한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 함수 구조를 사용하지만, 각각 다른 환경을 기억하는 별도의 함수가 만들어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 다음과 같은 형태로 활용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function createApiClient(baseUrl) {
  return async function request(path, options) {
    const response = await fetch(`${baseUrl}${path}`, options);

    if (!response.ok) {
      throw new Error(&quot;API 요청에 실패했습니다.&quot;);
    }

    return response.json();
  };
}

const userApi = createApiClient(&quot;/api/users&quot;);
const orderApi = createApiClient(&quot;/api/orders&quot;);

userApi(&quot;/1&quot;);
orderApi(&quot;/recent&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;userApi는 &quot;/api/users&quot;를 기억하고, orderApi는 &quot;/api/orders&quot;를 기억한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 클로저를 사용하면 반복되는 설정 값을 매번 넘기지 않아도 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 클로저의 대표적인 활용 3: 이벤트 핸들러&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드에서는 이벤트 핸들러에서 클로저를 매우 자주 사용한다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function bindClickLogger(button, pageName) {
  button.addEventListener(&quot;click&quot;, function handleClick() {
    console.log(`${pageName} 페이지에서 버튼 클릭`);
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;handleClick 함수는 pageName을 기억한다. 실제 클릭 이벤트는 나중에 발생하지만, 핸들러는 자신이 등록될 당시의 환경에 접근할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에서도 같은 원리로 동작한다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function ProductCard({ product }) {
  function handleClick() {
    console.log(`${product.name} 클릭`);
  }

  return (
    &amp;lt;button onClick={handleClick}&amp;gt;
      상품 보기
    &amp;lt;/button&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;handleClick은 product에 접근한다. 이 역시 클로저다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 React에서 매일 클로저를 쓰고 있다. 다만 너무 자연스럽게 쓰고 있어서 클로저라고 의식하지 않을 뿐이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 클로저의 대표적인 활용 4: 디바운스와 쓰로틀&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디바운스와 쓰로틀은 클로저의 실무 활용을 보여주는 좋은 예시다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;디바운스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디바운스는 이벤트가 연속으로 발생할 때 마지막 이벤트만 처리하는 기법이다.&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;function debounce(callback, delay) {
  let timerId;

  return function (...args) {
    clearTimeout(timerId);

    timerId = setTimeout(() =&amp;gt; {
      callback(...args);
    }, delay);
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 예시는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;const search = debounce((keyword) =&amp;gt; {
  console.log(`검색어: ${keyword}`);
}, 300);

search(&quot;r&quot;);
search(&quot;re&quot;);
search(&quot;rea&quot;);
search(&quot;react&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 반환된 함수는 timerId, callback, delay를 기억한다. debounce 함수 실행은 이미 끝났지만, 반환된 함수가 계속 timerId에 접근하면서 이전 타이머를 취소할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 클로저의 힘이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;쓰로틀&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰로틀은 일정 시간 동안 한 번만 실행되도록 제한하는 기법이다.&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;function throttle(callback, delay) {
  let lastExecutedTime = 0;

  return function (...args) {
    const now = Date.now();

    if (now - lastExecutedTime &amp;gt;= delay) {
      lastExecutedTime = now;
      callback(...args);
    }
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크롤 이벤트나 리사이즈 이벤트처럼 짧은 시간에 매우 자주 발생하는 이벤트를 제어할 때 사용된다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;const handleScroll = throttle(() =&amp;gt; {
  console.log(&quot;스크롤 처리&quot;);
}, 1000);

window.addEventListener(&quot;scroll&quot;, handleScroll);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반환된 함수는 lastExecutedTime을 기억한다. 그래서 이전 실행 시점을 기준으로 다음 실행 여부를 판단할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. 반복문과 클로저: var가 만드는 고전적인 문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저를 설명할 때 자주 등장하는 문제가 있다.&lt;/p&gt;
&lt;pre class=&quot;matlab&quot;&gt;&lt;code&gt;for (var i = 0; i &amp;lt; 3; i++) {
  setTimeout(function () {
    console.log(i);
  }, 1000);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 사람이 0, 1, 2가 출력될 것이라고 예상한다. 하지만 실제 결과는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;3
3
3
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 그럴까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;var는 함수 스코프를 가진다. 반복문 블록마다 새로운 i가 만들어지는 것이 아니라, 하나의 i를 세 번의 콜백이 공유한다. setTimeout의 콜백이 실행될 때는 이미 반복문이 끝난 뒤이고, 그 시점의 i 값은 3이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하는 가장 간단한 방법은 let을 사용하는 것이다.&lt;/p&gt;
&lt;pre class=&quot;matlab&quot;&gt;&lt;code&gt;for (let i = 0; i &amp;lt; 3; i++) {
  setTimeout(function () {
    console.log(i);
  }, 1000);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;0
1
2
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;let은 블록 스코프를 가지며, 반복문에서는 각 반복마다 새로운 바인딩이 만들어진다. 그래서 각 콜백은 서로 다른 i를 기억한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거에는 IIFE를 사용해서 이 문제를 해결하기도 했다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;for (var i = 0; i &amp;lt; 3; i++) {
  (function (currentIndex) {
    setTimeout(function () {
      console.log(currentIndex);
    }, 1000);
  })(i);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;currentIndex는 IIFE가 실행될 때마다 새로 만들어지는 매개변수다. 각 콜백은 서로 다른 currentIndex를 기억하기 때문에 원하는 결과를 얻을 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;11. 비동기 코드와 클로저&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저는 비동기 코드에서도 매우 중요하다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function fetchUser(userId) {
  return fetch(`/api/users/${userId}`)
    .then((response) =&amp;gt; response.json())
    .then((user) =&amp;gt; {
      console.log(`${userId}번 유저 정보`, user);
    });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 then 콜백은 userId를 사용한다. 이 콜백은 나중에 실행되지만, fetchUser가 호출될 당시의 userId에 접근할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;async/await를 사용해도 마찬가지다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async function fetchUser(userId) {
  const response = await fetch(`/api/users/${userId}`);
  const user = await response.json();

  console.log(`${userId}번 유저 정보`, user);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 함수 내부의 코드는 중간에 멈췄다가 나중에 이어서 실행된다. 이때도 함수는 자신의 렉시컬 환경을 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 이 특성 덕분에 요청 당시의 인자, 설정값, 상태값을 비동기 콜백 안에서 사용할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;12. React에서 자주 만나는 stale closure&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React를 사용한다면 클로저를 반드시 깊게 이해해야 한다. 특히 useEffect, 이벤트 핸들러, 타이머, 비동기 요청에서 &lt;b&gt;stale closure&lt;/b&gt; 문제가 자주 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stale closure란, 함수가 최신 값이 아니라 &lt;b&gt;함수가 만들어질 당시의 오래된 값을 참조하는 현상&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 예제를 보자.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function Counter() {
  const [count, setCount] = useState(0);

  function handleAlert() {
    setTimeout(() =&amp;gt; {
      alert(count);
    }, 3000);
  }

  return (
    &amp;lt;&amp;gt;
      &amp;lt;p&amp;gt;{count}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;증가&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={handleAlert}&amp;gt;3초 뒤 알림&amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 count가 0일 때 &amp;ldquo;3초 뒤 알림&amp;rdquo; 버튼을 누르고, 바로 증가 버튼을 여러 번 눌렀다고 하자. 3초 뒤 알림에는 최신 count가 아니라 버튼을 누르던 시점의 count가 표시될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 그럴까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 함수 컴포넌트는 렌더링될 때마다 함수가 다시 실행된다. 각 렌더링은 자신만의 count 값을 가진다. handleAlert 안의 setTimeout 콜백은 그 렌더링 시점의 count를 기억한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, React의 상태가 바뀌었다고 해서 이미 만들어진 클로저가 자동으로 최신 값을 바라보는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하는 방법은 상황에 따라 다르다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방법 1: 함수형 업데이트 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태 업데이트가 이전 상태에 의존한다면 함수형 업데이트를 사용한다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;setCount((prev) =&amp;gt; prev + 1);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 코드는 문제가 생길 수 있다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;function handleClick() {
  setCount(count + 1);
  setCount(count + 1);
  setCount(count + 1);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 의도는 3 증가일 수 있지만, 실제로는 같은 count 값을 기준으로 업데이트가 계산될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 안전한 방식은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;function handleClick() {
  setCount((prev) =&amp;gt; prev + 1);
  setCount((prev) =&amp;gt; prev + 1);
  setCount((prev) =&amp;gt; prev + 1);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방법 2: 최신 값을 ref에 저장&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타이머나 이벤트 리스너에서 최신 값을 계속 참조해야 한다면 useRef를 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function Counter() {
  const [count, setCount] = useState(0);
  const latestCountRef = useRef(count);

  useEffect(() =&amp;gt; {
    latestCountRef.current = count;
  }, [count]);

  function handleAlert() {
    setTimeout(() =&amp;gt; {
      alert(latestCountRef.current);
    }, 3000);
  }

  return (
    &amp;lt;&amp;gt;
      &amp;lt;p&amp;gt;{count}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount((prev) =&amp;gt; prev + 1)}&amp;gt;증가&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={handleAlert}&amp;gt;3초 뒤 알림&amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ref.current는 렌더링 사이에서도 같은 객체를 유지한다. 그래서 비동기 콜백 안에서 최신 값을 읽어야 할 때 유용하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방법 3: 의존성 배열을 정확히 작성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect에서 외부 값을 사용한다면 의존성 배열을 정확히 작성해야 한다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;useEffect(() =&amp;gt; {
  const id = setInterval(() =&amp;gt; {
    console.log(count);
  }, 1000);

  return () =&amp;gt; clearInterval(id);
}, [count]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;count를 의존성 배열에 넣지 않으면, effect 내부의 콜백이 오래된 count를 계속 참조할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 의존성 배열에 값을 넣으면 effect가 자주 재실행될 수 있다. 이때는 최신 값을 ref로 관리할지, effect 구조를 바꿀지, 상태 업데이트 방식을 바꿀지 판단해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에서 클로저를 이해한다는 것은 단순히 JavaScript 개념을 아는 것을 넘어, 렌더링과 상태의 스냅샷 모델을 이해한다는 뜻이기도 하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;13. 클로저와 메모리 관리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저는 매우 유용하지만, 잘못 사용하면 예상보다 오래 메모리를 잡고 있을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 예제를 보자.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function registerHandler() {
  const largeData = new Array(1000000).fill(&quot;data&quot;);

  const button = document.querySelector(&quot;#button&quot;);

  button.addEventListener(&quot;click&quot;, function handleClick() {
    console.log(largeData.length);
  });
}

registerHandler();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;registerHandler 함수 실행은 끝났다. 하지만 handleClick 이벤트 핸들러가 largeData를 참조하고 있다. 그리고 버튼에 이벤트 핸들러가 등록되어 있는 동안 handleClick은 살아 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과 largeData도 메모리에서 해제되지 않을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저 자체가 메모리 누수는 아니다. 문제는 &lt;b&gt;더 이상 필요 없는 데이터가 클로저를 통해 계속 참조되는 상황&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 리스너를 제거하면 참조를 끊을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function registerHandler() {
  const largeData = new Array(1000000).fill(&quot;data&quot;);
  const button = document.querySelector(&quot;#button&quot;);

  function handleClick() {
    console.log(largeData.length);
  }

  button.addEventListener(&quot;click&quot;, handleClick);

  return function cleanup() {
    button.removeEventListener(&quot;click&quot;, handleClick);
  };
}

const cleanup = registerHandler();

// 필요 없어졌을 때
cleanup();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에서는 effect cleanup이 같은 역할을 한다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;useEffect(() =&amp;gt; {
  function handleResize() {
    console.log(window.innerWidth);
  }

  window.addEventListener(&quot;resize&quot;, handleResize);

  return () =&amp;gt; {
    window.removeEventListener(&quot;resize&quot;, handleResize);
  };
}, []);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저를 사용할 때는 다음 질문을 해봐야 한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이 함수가 어떤 외부 값을 참조하고 있는가?&lt;/li&gt;
&lt;li&gt;그 외부 값이 큰 객체나 DOM 노드인가?&lt;/li&gt;
&lt;li&gt;이 함수는 언제까지 살아 있는가?&lt;/li&gt;
&lt;li&gt;필요 없어진 뒤 참조를 끊는 코드가 있는가?&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 질문을 할 수 있다면 클로저로 인한 메모리 문제를 훨씬 잘 다룰 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;14. 클로저는 상태 관리의 가장 작은 단위다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저를 단순히 면접용 개념으로만 보면 아쉽다. 클로저는 상태 관리의 가장 작은 원리 중 하나다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코드를 보자.&lt;/p&gt;
&lt;pre class=&quot;pf&quot;&gt;&lt;code&gt;function createStore(initialState) {
  let state = initialState;
  const listeners = new Set();

  function getState() {
    return state;
  }

  function setState(nextState) {
    state =
      typeof nextState === &quot;function&quot;
        ? nextState(state)
        : nextState;

    listeners.forEach((listener) =&amp;gt; listener(state));
  }

  function subscribe(listener) {
    listeners.add(listener);

    return function unsubscribe() {
      listeners.delete(listener);
    };
  }

  return {
    getState,
    setState,
    subscribe,
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 store를 만들었다.&lt;/p&gt;
&lt;pre class=&quot;pf&quot;&gt;&lt;code&gt;const store = createStore({ count: 0 });

const unsubscribe = store.subscribe((state) =&amp;gt; {
  console.log(&quot;state changed:&quot;, state);
});

store.setState((prev) =&amp;gt; ({ count: prev.count + 1 }));
store.setState((prev) =&amp;gt; ({ count: prev.count + 1 }));

console.log(store.getState()); // { count: 2 }

unsubscribe();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 state와 listeners는 외부에서 직접 접근할 수 없다. 하지만 getState, setState, subscribe는 같은 렉시컬 환경을 공유하면서 상태를 읽고 변경한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redux, Zustand 같은 상태관리 라이브러리의 내부 구현은 훨씬 복잡하지만, 기본적인 아이디어는 이와 크게 다르지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저는 단순한 문법 기능이 아니라, JavaScript에서 상태와 행위를 묶어 추상화하는 핵심 메커니즘이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;15. 클로저와 모듈 패턴&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES Module이 보편화되기 전에는 클로저를 활용한 모듈 패턴이 많이 사용되었다.&lt;/p&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;const userModule = (function () {
  let currentUser = null;

  function login(user) {
    currentUser = user;
  }

  function logout() {
    currentUser = null;
  }

  function getCurrentUser() {
    return currentUser;
  }

  return {
    login,
    logout,
    getCurrentUser,
  };
})();

userModule.login({ id: 1, name: &quot;Hyun&quot; });

console.log(userModule.getCurrentUser());
console.log(userModule.currentUser); // undefined
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IIFE를 실행해 private한 스코프를 만들고, 외부에 필요한 메서드만 반환한다. 이 메서드들은 모두 같은 private 상태인 currentUser를 공유한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현대 JavaScript에서는 ES Module과 #private class field 등 다른 선택지도 있다. 그래도 클로저 기반 모듈 패턴을 이해하면 JavaScript가 추상화를 어떻게 구현하는지 더 잘 이해할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;16. 클로저와 this는 다르다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저를 공부하다 보면 this와 헷갈릴 때가 있다. 하지만 둘은 완전히 다른 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저는 함수가 선언된 렉시컬 환경을 기억하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;this는 함수가 어떻게 호출되었는지에 따라 결정되는 실행 컨텍스트의 값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어보자.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const user = {
  name: &quot;Hyun&quot;,

  sayLater() {
    setTimeout(function () {
      console.log(this.name);
    }, 1000);
  },
};

user.sayLater(); // undefined 또는 예상과 다른 값
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setTimeout 안의 일반 함수에서 this는 user를 가리키지 않는다. 이 문제는 클로저와 직접적인 문제가 아니라 this 바인딩 문제다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거에는 다음처럼 this를 변수에 담아 클로저로 해결했다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const user = {
  name: &quot;Hyun&quot;,

  sayLater() {
    const self = this;

    setTimeout(function () {
      console.log(self.name);
    }, 1000);
  },
};

user.sayLater(); // Hyun
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현대 JavaScript에서는 화살표 함수를 사용해 더 간단히 해결할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const user = {
  name: &quot;Hyun&quot;,

  sayLater() {
    setTimeout(() =&amp;gt; {
      console.log(this.name);
    }, 1000);
  },
};

user.sayLater(); // Hyun
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화살표 함수는 자신만의 this를 만들지 않고, 바깥 스코프의 this를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;클로저: 함수가 선언된 환경의 변수에 접근하는 능력
this: 함수가 호출되는 방식에 따라 결정되는 값
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;17. 클로저를 사용할 때 흔히 하는 오해&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오해 1. 클로저는 내부 함수가 반환될 때만 생긴다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니다. 함수가 외부 스코프의 값을 참조하면 클로저의 성격이 나타난다. 반환되는 내부 함수는 클로저를 가장 쉽게 관찰할 수 있는 대표적인 패턴일 뿐이다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function outer() {
  const message = &quot;hello&quot;;

  setTimeout(function () {
    console.log(message);
  }, 1000);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 내부 함수는 반환되지 않는다. 하지만 setTimeout 콜백은 message를 기억한다. 이것도 클로저다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오해 2. 클로저는 값을 복사한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니다. 클로저는 값을 복사하는 것이 아니라 변수 바인딩이 들어 있는 환경에 접근한다.&lt;/p&gt;
&lt;pre class=&quot;ceylon&quot;&gt;&lt;code&gt;let value = 1;

function logValue() {
  console.log(value);
}

value = 2;

logValue(); // 2
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오해 3. 클로저는 항상 메모리 누수를 만든다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니다. 클로저는 정상적인 언어 기능이다. 문제는 필요 없어진 함수나 이벤트 리스너가 큰 객체를 계속 참조할 때 발생한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오해 4. 클로저는 면접용 개념일 뿐이다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니다. 디바운스, 쓰로틀, 이벤트 핸들러, React Hooks, 상태관리, 모듈 패턴, 함수형 프로그래밍 등 프론트엔드 실무 전반에서 사용된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;18. 면접에서 클로저를 어떻게 설명하면 좋을까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접에서는 너무 길게 설명하기보다, 핵심을 정확히 말하고 예시로 증명하는 것이 좋다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주니어 레벨 답변&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저는 함수가 선언될 당시의 외부 변수에 접근할 수 있는 JavaScript의 특성입니다. 외부 함수의 실행이 끝난 뒤에도 내부 함수가 외부 함수의 변수에 접근할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function outer() {
  let count = 0;

  return function inner() {
    count += 1;
    return count;
  };
}

const counter = outer();

counter(); // 1
counter(); // 2
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;미드 레벨 답변&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저는 함수와 그 함수가 생성될 당시의 렉시컬 환경이 함께 묶인 구조입니다. JavaScript는 렉시컬 스코프를 따르기 때문에 함수가 어디서 호출되는지가 아니라 어디서 선언되었는지를 기준으로 외부 변수에 접근합니다. 클로저는 디바운스, 쓰로틀, 이벤트 핸들러, private state 구현 등에 자주 사용됩니다. 다만 클로저가 큰 객체를 계속 참조하면 메모리 해제가 지연될 수 있어 이벤트 리스너 정리 같은 처리가 필요합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시니어 레벨 답변&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저는 함수 객체가 생성될 때 자신의 외부 렉시컬 환경을 참조하면서 발생하는 구조입니다. 중요한 점은 값을 복사하는 것이 아니라 변수 바인딩이 있는 환경을 참조한다는 것입니다. 이 특성 덕분에 상태 은닉, 함수 팩토리, 모듈 패턴, 비동기 콜백, React Hooks 같은 패턴을 구현할 수 있습니다. 반대로 React에서는 각 렌더링이 별도의 상태 스냅샷을 만들기 때문에 stale closure 문제가 발생할 수 있고, 타이머나 이벤트 리스너에서는 오래된 상태를 참조하지 않도록 의존성 배열, 함수형 업데이트, ref 등을 적절히 선택해야 합니다. 또한 클로저가 참조하는 객체는 함수가 살아 있는 동안 GC 대상이 되지 않을 수 있으므로, 장기적으로 살아 있는 핸들러에서는 참조 범위와 cleanup을 신경 써야 합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;19. 클로저를 제대로 이해했는지 확인하는 질문&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 질문에 답할 수 있다면 클로저를 꽤 깊게 이해한 것이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;클로저는 값을 저장하는가, 변수 바인딩을 참조하는가?&lt;/li&gt;
&lt;li&gt;함수가 호출되는 위치와 선언되는 위치 중 무엇이 스코프를 결정하는가?&lt;/li&gt;
&lt;li&gt;var 반복문에서 setTimeout 콜백이 모두 같은 값을 출력하는 이유는 무엇인가?&lt;/li&gt;
&lt;li&gt;let은 왜 반복문 클로저 문제를 해결할 수 있는가?&lt;/li&gt;
&lt;li&gt;React에서 stale closure는 왜 발생하는가?&lt;/li&gt;
&lt;li&gt;useEffect 의존성 배열을 잘못 작성하면 어떤 문제가 생기는가?&lt;/li&gt;
&lt;li&gt;클로저가 메모리 누수로 이어지는 경우는 언제인가?&lt;/li&gt;
&lt;li&gt;디바운스 함수는 어떤 변수를 클로저로 기억하는가?&lt;/li&gt;
&lt;li&gt;private state와 클로저는 어떤 관계가 있는가?&lt;/li&gt;
&lt;li&gt;클로저와 this는 어떻게 다른가?&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;20. 마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저는 JavaScript의 어려운 개념 중 하나로 자주 소개된다. 하지만 본질은 단순하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;함수는 자신이 선언된 환경을 기억한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 한 문장을 정확히 이해하면 많은 JavaScript 동작이 자연스럽게 연결된다. 비동기 콜백이 외부 변수에 접근하는 이유, 이벤트 핸들러가 특정 데이터를 기억하는 이유, 디바운스가 이전 타이머를 취소할 수 있는 이유, React에서 오래된 상태가 참조되는 이유가 모두 클로저와 연결된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저는 단순히 면접 질문 하나를 통과하기 위한 지식이 아니다. JavaScript로 상태를 다루고, 추상화를 만들고, UI 이벤트와 비동기 흐름을 설계하기 위한 핵심 기반이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발자로 성장하고 싶다면 클로저를 &amp;ldquo;외워야 할 개념&amp;rdquo;이 아니라 &amp;ldquo;코드를 읽는 렌즈&amp;rdquo;로 이해해야 한다.&lt;/p&gt;</description>
      <category>Frontend/JavaScript</category>
      <category>javascript</category>
      <category>javascript closure</category>
      <category>JavaScript 심화</category>
      <category>react hooks</category>
      <category>Stale Closure</category>
      <category>렉시컬 스코프</category>
      <category>메모리 관리</category>
      <category>비동기 프로그래밍</category>
      <category>클로저</category>
      <category>프론트엔드 개발</category>
      <author>토리나나</author>
      <guid isPermaLink="true">https://khys.tistory.com/111</guid>
      <comments>https://khys.tistory.com/111#entry111comment</comments>
      <pubDate>Sat, 20 Jun 2026 20:26:12 +0900</pubDate>
    </item>
    <item>
      <title>현업에서 AI 에이전트를 활용하는 방법</title>
      <link>https://khys.tistory.com/110</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;AI를 활용한 개발이 이제는 낯선 이야기가 아니게 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;977&quot; data-start=&quot;688&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;처음에는 자동 완성이나 간단한 질의응답 정도로 시작했지만, 최근에는 한 단계 더 나아가 에이전트(agent) 형태의 도구를 적극적으로 사용하는 개발자들이 많아졌다. 나 역시 최근에는 GitHub Copilot 에이전트 모드를 사용하거나, Claude Code를 통해 실제 코드베이스를 탐색하고 수정하는 흐름을 자주 사용하고 있다. 그리고&amp;nbsp;&lt;span style=&quot;text-align: start;&quot;&gt;체감상&lt;/span&gt; Claude Opus 4.6 모델이 꽤 좋은 결과를 보여줬다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;977&quot; data-start=&quot;688&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1122&quot; data-start=&quot;1053&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;이번 글에서는 현업에서 AI 에이전트를 어떻게 활용하고 있는지, 그리고 잘 쓰기 위해 어떤 관점이 필요한지 정리해보려 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;claude-opus-4.6.jpg&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;470&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eCyCdT/dJMcabDusjN/UvP25CnvHz3t2ANWdk8qC0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eCyCdT/dJMcabDusjN/UvP25CnvHz3t2ANWdk8qC0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eCyCdT/dJMcabDusjN/UvP25CnvHz3t2ANWdk8qC0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeCyCdT%2FdJMcabDusjN%2FUvP25CnvHz3t2ANWdk8qC0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;261&quot; data-filename=&quot;claude-opus-4.6.jpg&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;470&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-end=&quot;1141&quot; data-start=&quot;1124&quot; data-section-id=&quot;s9u03t&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;AI 에이전트란 무엇일까?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-end=&quot;1244&quot; data-start=&quot;1143&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;간단히 말하자면 AI 에이전트는 &lt;b&gt;답변만 하는 모델이 아니라, 주어진 목표를 해결하기 위해 스스로 탐색하고 도구를 사용하며 여러 단계를 수행하는 형태의 AI&lt;/b&gt;라고 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;1244&quot; data-start=&quot;1143&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1576&quot; data-start=&quot;1246&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;기존의 챗봇이 질문 하나에 답변 하나를 주는 데 가까웠다면, 에이전트는 그보다 조금 더 행동에 가깝다. 예를 들어 단순히 &amp;ldquo;이 에러 원인이 뭐야?&amp;rdquo;라고 설명하는 데 그치는 것이 아니라, 실제로 프로젝트 파일을 읽고, 관련 코드를 찾고, 수정안을 만들고, 필요한 경우 명령 실행이나 테스트까지 이어가는 식이다. GitHub Copilot 문서도 IDE의 agent mode를 &amp;ldquo;특정 작업을 주면 어떤 파일을 수정할지 결정하고, 코드 변경과 터미널 명령을 제안하며, 문제가 있으면 반복적으로 수정하는 방식&amp;rdquo;으로 설명하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;1576&quot; data-start=&quot;1246&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1648&quot; data-start=&quot;1578&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;즉, 에이전트의 핵심은 &amp;ldquo;더 똑똑한 답변&amp;rdquo;이 아니라 &lt;b&gt;문제를 해결하기 위한 작업 단위를 스스로 이어갈 수 있느냐&lt;/b&gt;에 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;1648&quot; data-start=&quot;1578&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;2156&quot; data-start=&quot;2134&quot; data-section-id=&quot;1mq574s&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;현업에서 가장 자주 쓰는 활용 사례&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-end=&quot;2187&quot; data-start=&quot;2158&quot; data-section-id=&quot;1nbuwtg&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;1. 처음 보는 코드베이스를 빠르게 탐색할 때&lt;/span&gt;&lt;/h3&gt;
&lt;p data-end=&quot;2221&quot; data-start=&quot;2189&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;현업에서는 내가 직접 작성하지 않은 코드가 훨씬 더 많다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;2281&quot; data-start=&quot;2223&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;프로젝트에 새로 합류했거나, 예전에 만들어 둔 기능을 다시 볼 때 가장 먼저 드는 생각은 대개 비슷하다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-end=&quot;2349&quot; data-start=&quot;2283&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&amp;ldquo;이 로직이 어디서 시작되는 거지?&amp;rdquo;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&amp;ldquo;상태는 어디서 바뀌는 거지?&amp;rdquo;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&amp;ldquo;이 화면은 어느 API 응답을 바라보고 있지?&amp;rdquo;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-end=&quot;2369&quot; data-start=&quot;2351&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;이럴 때 에이전트는 꽤 강력하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;2542&quot; data-start=&quot;2371&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;단순히 검색창에 키워드를 넣는 것보다, &amp;ldquo;사용자 프로필 수정 플로우가 어디서 시작되고 어떤 API를 거쳐서 어떤 상태를 변경하는지 설명해줘&amp;rdquo;처럼 요청하면 코드 흐름을 훨씬 빠르게 잡아준다. 특히 폴더 구조가 크고 관심사 분리가 복잡한 프로젝트일수록, 에이전트는 사람의 첫 탐색 비용을 줄여주는 역할을 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;2615&quot; data-start=&quot;2544&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;내가 체감하기로 에이전트의 첫 번째 가치는 &amp;ldquo;코드를 대신 짜주는 것&amp;rdquo;보다 &amp;ldquo;코드베이스를 빨리 이해하게 해주는 것&amp;rdquo;에 더 가깝다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;2615&quot; data-start=&quot;2544&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;2640&quot; data-start=&quot;2617&quot; data-section-id=&quot;1l79g12&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;2. 반복적인 수정 작업을 맡길 때&lt;/span&gt;&lt;/h3&gt;
&lt;p data-end=&quot;2682&quot; data-start=&quot;2642&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;현업의 많은 작업은 사실 창의적인 알고리즘 설계보다 반복 수정에 가깝다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;2702&quot; data-start=&quot;2684&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;예를 들면 아래와 같은 일들이다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2872&quot; data-start=&quot;2704&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2735&quot; data-start=&quot;2704&quot; data-section-id=&quot;1xx526z&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;여러 컴포넌트의 props 이름을 일괄 변경하는 작업&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2757&quot; data-start=&quot;2736&quot; data-section-id=&quot;14yow37&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;공통 유틸 함수로 로직을 묶는 작업&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2795&quot; data-start=&quot;2758&quot; data-section-id=&quot;3oypjd&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;API 응답 타입이 바뀌었을 때 관련 타입을 전파 수정하는 작업&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2833&quot; data-start=&quot;2796&quot; data-section-id=&quot;1ubga54&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;로딩, 에러, 빈 상태 UI를 여러 화면에 동일하게 붙이는 작업&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2872&quot; data-start=&quot;2834&quot; data-section-id=&quot;1kxrm8m&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;콘솔 로그 제거, 불필요한 import 정리, lint 경고 해소&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2948&quot; data-start=&quot;2874&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;이런 작업은 사람이 해도 할 수 있지만, 집중력이 빨리 소모된다. 반면 에이전트는 명확한 규칙만 주어지면 생각보다 꾸준하게 처리한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;2970&quot; data-start=&quot;2950&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;예를 들어 아래처럼 요청할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;users 관련 화면 전체에서 isLoading, isError, data 분기 패턴을 공통 훅으로 추출해줘.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;기존 UI 구조는 유지하고, 파일 변경 이유를 각 파일마다 설명해줘.&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3185&quot; data-start=&quot;3086&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;중요한 점은 막연하게 &amp;ldquo;리팩터링해줘&amp;rdquo;라고 던지는 것보다, &lt;b&gt;범위와 제약을 같이 주는 것&lt;/b&gt;이다. 에이전트는 자유도가 너무 높아지면 오히려 불필요한 수정까지 하려는 경향이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;3185&quot; data-start=&quot;3086&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;3211&quot; data-start=&quot;3187&quot; data-section-id=&quot;yq40fu&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;3. 리팩터링 초안을 빠르게 잡을 때&lt;/span&gt;&lt;/h3&gt;
&lt;p data-end=&quot;3238&quot; data-start=&quot;3213&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;리팩터링은 방향을 잡는 데 시간이 많이 든다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;3327&quot; data-start=&quot;3240&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;특히 &amp;ldquo;어디까지 추상화해야 하는가&amp;rdquo;, &amp;ldquo;이 로직을 훅으로 빼는 게 맞는가&amp;rdquo;, &amp;ldquo;공통 컴포넌트로 묶는 순간 오히려 복잡해지지 않는가&amp;rdquo; 같은 판단은 늘 어렵다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;3383&quot; data-start=&quot;3329&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;이때 에이전트는 정답을 주기보다 &lt;b&gt;초안 몇 개를 빠르게 비교하게 해주는 도구&lt;/b&gt;로 쓸 때 좋다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;3429&quot; data-start=&quot;3385&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;예를 들어 어떤 화면 컴포넌트가 지나치게 비대해졌다면 아래처럼 요청할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;이 컴포넌트를 관심사 기준으로 분리하는 방법을 3가지 제안해줘.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;각 방법의 장단점과, 지금 구조에서 가장 부작용이 적은 안을 추천해줘.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;그리고 추천안 기준으로 실제 파일 분리 초안까지 만들어줘.&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3602&quot; data-start=&quot;3553&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;이렇게 하면 바로 완성본을 받는 것이 아니라, 설계안 자체를 비교하면서 생각할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;3698&quot; data-start=&quot;3604&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;개인적으로는 이 지점이 꽤 중요하다고 본다. 에이전트는 &amp;ldquo;내 생각을 대체하는 도구&amp;rdquo;가 아니라, &lt;b&gt;설계 후보를 빠르게 늘려서 비교 비용을 줄여주는 도구&lt;/b&gt;에 더 가깝다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;3698&quot; data-start=&quot;3604&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;3725&quot; data-start=&quot;3700&quot; data-section-id=&quot;necomc&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;4. 테스트와 디버깅 보조에 사용할 때&lt;/span&gt;&lt;/h3&gt;
&lt;p data-end=&quot;3755&quot; data-start=&quot;3727&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;버그 수정은 흔히 &amp;ldquo;원인을 안다&amp;rdquo;고 끝나지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;3776&quot; data-start=&quot;3757&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;실제로는 아래 질문들이 더 어렵다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3863&quot; data-start=&quot;3778&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3802&quot; data-start=&quot;3778&quot; data-section-id=&quot;166mqkc&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;이 수정이 다른 화면에는 영향이 없을까?&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;3842&quot; data-start=&quot;3803&quot; data-section-id=&quot;eeeyez&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;지금 깨진 것은 증상이고, 진짜 원인은 더 아래에 있는 것 아닐까?&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;3863&quot; data-start=&quot;3843&quot; data-section-id=&quot;z795tv&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;테스트를 어디까지 써야 안전할까?&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3896&quot; data-start=&quot;3865&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;이럴 때 에이전트는 디버깅 보조 역할을 꽤 잘 수행한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;4009&quot; data-start=&quot;3898&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;에러 메시지와 관련 파일 몇 개를 주고 &amp;ldquo;가능한 원인을 우선순위대로 정리해줘&amp;rdquo;, &amp;ldquo;재현 경로를 추정해줘&amp;rdquo;, &amp;ldquo;가장 작은 수정으로 막을 수 있는 방법부터 제안해줘&amp;rdquo;라고 하면, 생각 정리에 도움이 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;4036&quot; data-start=&quot;4011&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;또 수정 이후에는 다음처럼 활용할 수도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;이 수정이 회귀 버그를 만들 수 있는 지점을 체크리스트로 정리해줘.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;가능하면 테스트 코드 초안도 함께 작성해줘.&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4200&quot; data-start=&quot;4114&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;물론 테스트 코드가 항상 바로 통과하는 수준으로 나오는 것은 아니다. 하지만 최소한 &amp;ldquo;무엇을 검증해야 하는지&amp;rdquo;를 빠르게 정리해준다는 점에서 생산성이 높다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;4200&quot; data-start=&quot;4114&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;4227&quot; data-start=&quot;4202&quot; data-section-id=&quot;1vawaol&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;5. 문서화와 인수인계 자료를 만들 때&lt;/span&gt;&lt;/h3&gt;
&lt;p data-end=&quot;4268&quot; data-start=&quot;4229&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;현업에서는 코드를 작성하는 시간만큼, 그것을 설명하는 시간도 중요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;4301&quot; data-start=&quot;4270&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;특히 기능을 배포하고 나면 아래 문서들이 자주 필요하다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4362&quot; data-start=&quot;4303&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4310&quot; data-start=&quot;4303&quot; data-section-id=&quot;176igrv&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;PR 설명&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;4319&quot; data-start=&quot;4311&quot; data-section-id=&quot;1trsq54&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;릴리즈 노트&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;4330&quot; data-start=&quot;4320&quot; data-section-id=&quot;yz0668&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;기능 명세 초안&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;4341&quot; data-start=&quot;4331&quot; data-section-id=&quot;amcxzk&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;QA 체크리스트&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;4349&quot; data-start=&quot;4342&quot; data-section-id=&quot;1vjgd6o&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;회고 문서&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;4362&quot; data-start=&quot;4350&quot; data-section-id=&quot;1j97ugw&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;팀원 인수인계 문서&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4385&quot; data-start=&quot;4364&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;에이전트는 이 부분에서도 꽤 유용하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;4540&quot; data-start=&quot;4387&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;실제 수정 diff를 읽게 한 다음 &amp;ldquo;변경 목적, 영향 범위, QA 포인트를 정리해줘&amp;rdquo;라고 하면 초안이 금방 나온다. 특히 개발자는 구현 당시 머릿속에는 너무 익숙한 내용이라 오히려 설명문을 쓰기 어려운 경우가 많은데, 에이전트는 그 설명의 첫 문장을 열어주는 역할을 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;4540&quot; data-start=&quot;4387&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;4569&quot; data-start=&quot;4542&quot; data-section-id=&quot;1857tj0&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;그렇다면 에이전트에게 어디까지 맡겨도 될까?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-end=&quot;4588&quot; data-start=&quot;4571&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;여기서 중요한 점이 하나 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;4634&quot; data-start=&quot;4590&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;에이전트를 많이 쓴다고 해서, 무조건 많은 일을 맡기는 것이 좋은 것은 아니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;4701&quot; data-start=&quot;4636&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;오히려 현업에서는 &lt;b&gt;애매하게 어려운 문제&lt;/b&gt;일수록 전부 맡기기보다, 단위를 잘게 쪼개서 맡기는 편이 더 안정적이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;4791&quot; data-start=&quot;4703&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;예를 들어 &amp;ldquo;우리 서비스의 인증 아키텍처를 전면 개선해줘&amp;rdquo; 같은 요청은 범위도 넓고 판단 포인트도 많아서 실패하기 쉽다. 반면 아래처럼 나누면 훨씬 좋아진다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4911&quot; data-start=&quot;4793&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4814&quot; data-start=&quot;4793&quot; data-section-id=&quot;ubnhp6&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;현재 인증 플로우를 먼저 요약한다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;4829&quot; data-start=&quot;4815&quot; data-section-id=&quot;1tq05jp&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;취약한 지점을 찾는다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;4861&quot; data-start=&quot;4830&quot; data-section-id=&quot;jjav74&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;refresh token 처리 부분만 개선안을 낸다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;4887&quot; data-start=&quot;4862&quot; data-section-id=&quot;zq7tfv&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;타입/에러 처리/UI 영향 범위를 나눈다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;4911&quot; data-start=&quot;4888&quot; data-section-id=&quot;1w5ykxm&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;마지막에 실제 코드 수정으로 들어간다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4992&quot; data-start=&quot;4913&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;즉, 에이전트는 거대한 문제를 한 번에 해결하는 마법 도구라기보다, &lt;b&gt;잘게 나눈 작업을 빠르게 이어서 처리하는 도구&lt;/b&gt;라고 보는 편이 맞다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;4992&quot; data-start=&quot;4913&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;5019&quot; data-start=&quot;4994&quot; data-section-id=&quot;2rpjuu&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;잘 쓰는 사람과 잘 못 쓰는 사람의 차이&lt;/span&gt;&lt;/h2&gt;
&lt;p data-end=&quot;5061&quot; data-start=&quot;5021&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;AI 에이전트를 잘 쓰는 사람은 프롬프트를 화려하게 쓰는 사람이 아니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;5085&quot; data-start=&quot;5063&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;오히려 아래 세 가지를 잘하는 사람이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;5085&quot; data-start=&quot;5063&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;5110&quot; data-start=&quot;5087&quot; data-section-id=&quot;cedy0h&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;1. 작업의 범위를 명확하게 자른다&lt;/span&gt;&lt;/h3&gt;
&lt;p data-end=&quot;5130&quot; data-start=&quot;5112&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;&amp;ldquo;리팩터링해줘&amp;rdquo;는 나쁜 요청이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;5201&quot; data-start=&quot;5132&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;&amp;ldquo;이 컴포넌트의 데이터 fetching 로직만 custom hook으로 분리하고, 기존 마크업은 유지해줘&amp;rdquo;는 좋은 요청이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;5242&quot; data-start=&quot;5203&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;좋은 요청은 목표가 작고, 변경 범위가 명확하고, 성공 조건이 보인다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;5242&quot; data-start=&quot;5203&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;5264&quot; data-start=&quot;5244&quot; data-section-id=&quot;bbu16e&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;2. 제약 조건을 분명히 준다&lt;/span&gt;&lt;/h3&gt;
&lt;p data-end=&quot;5283&quot; data-start=&quot;5266&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;현업 코드에는 늘 제약이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;5389&quot; data-start=&quot;5285&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;5304&quot; data-start=&quot;5285&quot; data-section-id=&quot;73bf2u&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;라이브러리를 추가하면 안 된다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;5328&quot; data-start=&quot;5305&quot; data-section-id=&quot;1y29sgz&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;public API는 바꾸면 안 된다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;5351&quot; data-start=&quot;5329&quot; data-section-id=&quot;1f7x8y&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;디자인 시스템 컴포넌트만 써야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;5370&quot; data-start=&quot;5352&quot; data-section-id=&quot;eg2oxu&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;기존 테스트를 깨면 안 된다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;5389&quot; data-start=&quot;5371&quot; data-section-id=&quot;f8zacy&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;성능 특성이 바뀌면 안 된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;5433&quot; data-start=&quot;5391&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;이 제약을 빼먹으면 에이전트는 너무 쉽게 &amp;ldquo;그럴듯하지만 곤란한 답&amp;rdquo;을 낸다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;5433&quot; data-start=&quot;5391&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;5456&quot; data-start=&quot;5435&quot; data-section-id=&quot;1662axn&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;3. 검토는 반드시 사람이 한다&lt;/span&gt;&lt;/h3&gt;
&lt;p data-end=&quot;5481&quot; data-start=&quot;5458&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;이 부분은 아무리 강조해도 지나치지 않다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;5583&quot; data-start=&quot;5483&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;에이전트는 상당히 유용하지만, 여전히 코드의 의미와 서비스의 책임을 최종적으로 지는 주체는 개발자다. 특히 인증, 결제, 권한, 데이터 정합성처럼 중요한 영역에서는 더더욱 그렇다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;5622&quot; data-start=&quot;5585&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;나는 개인적으로 AI 에이전트를 사용할 때 아래 기준을 갖고 본다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;5712&quot; data-start=&quot;5624&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;5643&quot; data-start=&quot;5624&quot; data-section-id=&quot;9vt9mk&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;구조 제안은 적극적으로 받는다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;5660&quot; data-start=&quot;5644&quot; data-section-id=&quot;1lsojhk&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;반복 수정은 넓게 맡긴다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;5687&quot; data-start=&quot;5661&quot; data-section-id=&quot;13xhnby&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;핵심 비즈니스 로직은 사람이 중심을 잡는다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;5712&quot; data-start=&quot;5688&quot; data-section-id=&quot;pokizv&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;머지 전 최종 판단은 반드시 직접 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;5730&quot; data-start=&quot;5714&quot; data-section-id=&quot;1w2eusl&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;실제로 써보며 느낀 한계&lt;/span&gt;&lt;/h2&gt;
&lt;p data-end=&quot;5761&quot; data-start=&quot;5732&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;에이전트가 잘하는 것도 많지만, 당연히 한계도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;5813&quot; data-start=&quot;5763&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;가장 대표적인 것은 &lt;b&gt;문제의 본질을 잘못 정의한 채 부지런히 일할 수 있다는 점&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;5842&quot; data-start=&quot;5815&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;즉, 방향이 틀리면 열심히 틀린 답을 만들어낸다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;5842&quot; data-start=&quot;5815&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;5963&quot; data-start=&quot;5844&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;예를 들어 어떤 버그의 진짜 원인이 상태 동기화 문제인데, 에이전트가 화면 컴포넌트만 계속 고치고 있을 수도 있다. 또는 현재 프로젝트의 컨벤션을 충분히 이해하지 못한 채, 코드 모양만 그럴듯하게 맞출 수도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;6040&quot; data-start=&quot;5965&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그래서 에이전트를 쓸 때 중요한 것은 &amp;ldquo;얼마나 많이 시키는가&amp;rdquo;가 아니라, &lt;b&gt;중간중간 올바른 방향으로 가고 있는지 체크하는 것&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;6040&quot; data-start=&quot;5965&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;6126&quot; data-start=&quot;6042&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;이 점에서는 오히려 사람 시니어 개발자와 일하는 감각과 비슷하다. 일을 던져놓고 기다리는 것이 아니라, 중간 산출물을 보면서 계속 방향을 맞춰야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;6126&quot; data-start=&quot;6042&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;6156&quot; data-start=&quot;6128&quot; data-section-id=&quot;11r5772&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;결국 중요한 것은 도구보다 작업 분해 능력이다&lt;/span&gt;&lt;/h2&gt;
&lt;p data-end=&quot;6203&quot; data-start=&quot;6158&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;요즘은 어떤 모델이 더 좋으냐, 어떤 에이전트 도구가 더 강하냐는 이야기가 많다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;6351&quot; data-start=&quot;6205&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;물론 체감 차이는 분명히 있다. 실제로 GitHub Copilot은 여러 모델을 지원하고, Claude 쪽도 Claude Code와 Agent SDK, MCP 같은 흐름을 꾸준히 확장하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;6351&quot; data-start=&quot;6205&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;6468&quot; data-start=&quot;6353&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;하지만 현업에서 생산성을 가장 크게 가르는 것은 모델 이름 자체보다, &lt;b&gt;개발자가 문제를 얼마나 잘 쪼개고, 제약을 얼마나 명확히 전달하며, 결과를 얼마나 비판적으로 검토하는가&lt;/b&gt;에 더 가깝다고 생각한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;6468&quot; data-start=&quot;6353&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;6525&quot; data-start=&quot;6470&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;AI 에이전트는 개발자를 대체하는 존재라기보다, 개발자의 사고 과정을 더 빠르게 순환시키는 도구다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;6649&quot; data-start=&quot;6527&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;코드를 대신 쳐주는 도구로만 보면 생각보다 금방 한계를 느낄 수 있다. 반대로 코드베이스 탐색, 반복 수정, 리팩터링 초안 작성, 테스트 보조, 문서화까지 포함한 &lt;b&gt;작업 흐름 전체의 가속 장치&lt;/b&gt;로 보면 꽤 강력하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;6649&quot; data-start=&quot;6527&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;6717&quot; data-start=&quot;6651&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;결국 중요한 것은 &amp;ldquo;AI가 얼마나 똑똑한가&amp;rdquo;보다, &lt;b&gt;내가 이 도구를 어떤 단위의 일에 어떻게 연결할 수 있는가&lt;/b&gt;다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;6780&quot; data-start=&quot;6719&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;나 역시 최근에는 AI 에이전트를 단순한 보조 도구가 아니라, 하나의 실무용 인터페이스처럼 다루게 되고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;6890&quot; data-start=&quot;6782&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;아마 앞으로의 개발 생산성 차이는 코드를 한 줄 더 빨리 치는 사람보다, &lt;b&gt;AI 에이전트에게 더 정확한 작업을 맡기고 더 정확하게 검토할 수 있는 사람&lt;/b&gt;에게서 더 크게 벌어지지 않을까 싶다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>AI</category>
      <category>ai에이전트</category>
      <category>claudecode</category>
      <category>ClaudeOpus</category>
      <category>GitHubCopilot</category>
      <author>토리나나</author>
      <guid isPermaLink="true">https://khys.tistory.com/110</guid>
      <comments>https://khys.tistory.com/110#entry110comment</comments>
      <pubDate>Tue, 17 Mar 2026 20:10:55 +0900</pubDate>
    </item>
    <item>
      <title>클린 코드가 모호한 개념인 이유, 선언적 프로그래밍과 추상화</title>
      <link>https://khys.tistory.com/108</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;클린 코드 (Clean code)&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; background-color: #ffffff;&quot;&gt;클린 코드는 가독성, 단순성 및 유지 관리 용이성을 강조하는 프로그래밍 스타일이다. 클린 코드의 원칙은 널리 받아들여지고 있지만 클린 코드의 개념은 다소 모호하고 해석의 여지가 있다. 프로그래머마다 무엇이 클린 코드를 구성하는지에 대해 서로 다른 의견을 가질 수 있으며 한 컨텍스트에서 클린 코드로 간주되는 것이 다른 컨텍스트에서는 클린 코드로 간주되지 않을 수 있다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; background-color: #ffffff;&quot;&gt;종종 논의되는 클린 코드의 한 측면으로 주석 사용이 있다. 어떤 프로그래머는 클린 코드가 자명해야 하고 최소한의 주석이 필요하다고 믿는 반면, 다른 프로그래머는 주석이 코드를 문서화하고 다른 프로그래머가 더 쉽게 이해할 수 있도록 만드는 중요한 부분이라고 주장한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; background-color: #ffffff;&quot;&gt;논쟁의 또 다른 영역은 약어와 두문자어의 사용이다. 일부 프로그래머는 이것이 코드를 더 간결하고 읽기 쉽게 만들 수 있다고 주장하는 반면, 다른 사람들은 특히 사용된 약어와 두문자어에 익숙하지 않은 사람들이 코드를 이해하기 더 어렵게 만들 수 있다고 믿는다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; background-color: #ffffff;&quot;&gt;클린 코드를 작성할 때 코드의 가독성과 유지 관리 가능성에 주의를 기울이는 것이 중요하다. 여기에는 설명 변수 이름 사용, 코드를 논리 블록으로 구성, 공백 및 들여쓰기를 사용하여 코드 가독성을 향상하는 것이 포함된다. 일관된 코딩 스타일을 고수하고 불필요한 복잡성을 피하는 것과 같은 좋은 프로그래밍 관행을 따르는 것도 중요하다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; background-color: #ffffff;&quot;&gt;클린 코드의 주요 장점 중 하나는 이해하고 유지하기가 더 쉽다는 것이다. 깨끗한 코드를 변경하고 버그를 수정하는 것이 더 쉽기 때문에 장기적으로 시간과 노력을 절약할 수 있다. 정리가 잘 되어 있고 읽기 쉬운 코드에서 오류를 더 쉽게 발견할 수 있으므로 클린 코드는 정확할 가능성이 더 높다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; background-color: #ffffff;&quot;&gt;그러나 클린 코드에는 몇 가지 잠재적인 단점도 있다. 한 가지 우려 사항은 세부 사항에 더 많은 주의를 기울이고 가독성에 더 중점을 두어야 하기 때문에 코드를 작성하는 데 시간이 더 오래 걸릴 수 있다는 것이다. 또한 청결도와 성능 또는 효율성과 같은 기타 요인 사이에 트레이드 오프가 있을 수 있다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; background-color: #ffffff;&quot;&gt;결론적으로 클린 코드는 해석의 여지가 있는 주관적인 개념이다. 일반적으로 허용되는 클린 코드의 원칙이 있지만 클린 코드를 구성하는 요소는 코드의 컨텍스트와 목표에 따라 다를 수 있다. 클린 코드를 작성할 때 가독성, 유지 관리성 및 좋은 프로그래밍 방법에 주의를 기울이는 것이 중요하지만 성능 및 효율성과 같은 다른 요소를 고려하는 것도 중요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;선언적 프로그래밍 (Declarative programming), 추상화 (Abstraction)&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;선언적 프로그래밍은 프로그램이 어떻게 달성해야 하는지가 아니라 프로그램이 무엇을 달성해야 하는지에 초점을 맞추는 프로그래밍 패러다임이다. 선언적 프로그래밍에서 프로그래머는 원하는 결과를 지정하고 프로그래밍 언어 또는 프레임워크는 구현 세부 사항을 처리한다. 이렇게 하면 프로그래머가 코드 실행 방법에 대한 하위 수준 세부 사항에 대해 걱정할 필요가 없으므로 코드를 더 쉽게 읽고 이해할 수 있다.&lt;/span&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;540&quot; data-origin-height=&quot;352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dtScsm/btrVBnTb25t/njuZauBWI9uU7lYncMB591/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dtScsm/btrVBnTb25t/njuZauBWI9uU7lYncMB591/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dtScsm/btrVBnTb25t/njuZauBWI9uU7lYncMB591/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdtScsm%2FbtrVBnTb25t%2FnjuZauBWI9uU7lYncMB591%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;512&quot; height=&quot;334&quot; data-origin-width=&quot;540&quot; data-origin-height=&quot;352&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; background-color: #ffffff;&quot;&gt;추상화는 클린 코드의 또 다른 중요한 개념이다. 추상화에는 코드 조각의 기본 구현 세부 정보를 외부 동작과 분리하는 작업이 포함된다. 구현의 세부 사항을 추상화하면 프로그래머가 코드가 내부적으로 작동하는 방식에 대해 걱정할 필요가 없으므로 코드를 이해하고 유지 관리하기가 더 쉬워진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; background-color: #ffffff;&quot;&gt;&lt;br /&gt;선언적 프로그래밍 및 추상화는 코드를 더 간결하고 이해하기 쉽게 만드는 데 도움이 되므로 깔끔한 코드를 작성하는 데 유용한 방법이 될 수 있다. 선언적 프로그래밍을 사용함으로써 프로그래머는 구체적인 구현 방법보다는 원하는 코드 결과에 집중할 수 있다. 추상화 또한 구현 세부 사항을 숨기고 코드의 외부 동작에 집중함으로써 코드를 단순화하는 데 도움이 될 수 있다.&lt;br /&gt;&lt;br /&gt;그러나 선언적 프로그래밍과 추상화를 적절하게 사용하는 것이 중요하다. 이러한 기술을 과도하게 사용하면 너무 추상적이거나 이해하기 어려운 코드가 될 수 있다. 또한 추상화의 이점과 코드의 구현 세부 사항에 대한 제어를 유지해야 할 필요성 사이에서 균형을 유지하는 것이 중요하다.&lt;br /&gt;&lt;br /&gt;결론적으로 선언적 프로그래밍과 추상화는 클린 코드를 작성하는 데 유용한 기술이다. 원하는 결과에 집중하고 구현 세부 사항을 추상화하여 코드를 더 간결하고 이해하기 쉽게 만들 수 있다. 그러나 이러한 기술을 적절하게 사용하고 추상화와 제어 사이의 균형을 맞추는 것이 중요하다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Frontend</category>
      <category>선언적 프로그래밍</category>
      <category>추상화</category>
      <category>클린 코드</category>
      <author>토리나나</author>
      <guid isPermaLink="true">https://khys.tistory.com/108</guid>
      <comments>https://khys.tistory.com/108#entry108comment</comments>
      <pubDate>Sat, 7 Jan 2023 20:35:58 +0900</pubDate>
    </item>
    <item>
      <title>[JS] 값(value)에 의한 전달과 참조(reference)에 의한 전달</title>
      <link>https://khys.tistory.com/106</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;JavaScript의 자료형에는 기본(Primitive) 자료형과 기본이 아닌(Non-primitive) 자료형으로 두 가지 유형의 자료형이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;646&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0RbXx/btrVoGlBAo5/MhzDbf4TGmY0nakoFtIxOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0RbXx/btrVoGlBAo5/MhzDbf4TGmY0nakoFtIxOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0RbXx/btrVoGlBAo5/MhzDbf4TGmY0nakoFtIxOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0RbXx%2FbtrVoGlBAo5%2FMhzDbf4TGmY0nakoFtIxOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;512&quot; height=&quot;258&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;646&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;&lt;b&gt;기본 자료형&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;JavaScript 언어에서 제공하는 미리 정의된 데이터 유형을 기본 자료형이라고 한다. JavaScript는 Number, String, Boolean, Undefined, Symbol 및 BigInt를 포함하는 여섯 가지 유형의 기본 자료형을 제공한다. 기본 자료형의 크기는 고정되어 있으므로 JavaScript는 콜 스택(실행 컨텍스트)에 기본 자료형을 저장한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;기본 자료형에 액세스하면 해당 변수에 저장된 실제 값을 조작한다. 따라서 기본 변수는 값에 의해 액세스됩니다. 기본 값을 저장하는 변수를 다른 변수에 할당하면 변수에 저장된 값이 생성되어 새 변수에 복사된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;&lt;b&gt;참조 자료형&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;JavaScript 언어의 기본 자료형에서 파생된 데이터 유형을 참조 자료형이라고 한다. 기본이 아닌 자료형 또는 파생 자료형이라고도 한다. JavaScript는 Array, Object 및 Function을 포함하는 세 가지 유형의 참조 자료형을 제공한다. 참조 자료형의 크기는 동적이므로 힙에 저장된다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;참조 자료형에 액세스하면 저장된 실제 값이 아니라 참조를 통해 조작한다. 따라서 참조 값인 변수는 참조로 액세스된다. 한 변수에서 다른 변수로 참조 값을 할당할 때 변수에 저장된 값도 새 변수의 위치로 복사되지만, 차이점은 이제 두 변수에 저장된 값이 &lt;span style=&quot;background-color: #fdfdfd;&quot;&gt;힙에 저장된&lt;/span&gt; 실제 객체의 주소라는 것이다. 결과적으로 두 변수는 동일한 객체를 참조하고 있으므로, 두 변수에서 원래 객체를 조작할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;&lt;b&gt;값에 의한 전달&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;JavaScript에서 함수에 기본 자료형&amp;nbsp;값을 인수로 전달하면 인수의 값이 함수에 전달되는데, 이를 값에 의한 전달(pass by value)이라고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;다음은 JavaScript에서 값에 의한 전달의 예다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1672847363085&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function addOne(x) {
  x = x + 1;
  return x;
}

let a = 1;
let b = addOne(a);

console.log(a); // 1
console.log(b); // 2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;이 예제에서는 `a`의 값이 인수로 `addOne()` 함수에 전달된다. 이 함수는 `x` 값을 1씩 증가시키고 결과를 반환한다. 그러나 함수 외부의 값은 변경되지 않는다. 이는 `a`의 값이 값에 의한 전달로 함수에 전달되고 함수 내에서 `x`의 값을 변경해도 `a`의 값에 영향을 주지 않기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;참조에 의한 전달&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;JavaScript에서는 참조 자료형을 사용하여 참조에 의한 전달(pass by reference)을 구현할 수 있다. JavaScript에서 객체를 함수에 전달할 때 객체의 값은 여전히 값으로 전달된다. 그러나 객체의 값은 실제 객체 자체가 아니라 객체의 메모리 위치에 대한 참조라고 볼 수 있다. 즉, 함수 내의 객체에 대한 모든 변경 사항이 함수 외부의 원본 객체에 반영된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;다음은 JavaScript에서 참조에 의한 전달을 구현한 예다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1672847546346&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function addOne(obj) {
  obj.x = obj.x + 1;
}

let obj = { x: 1 };
addOne(obj);

console.log(obj.x); // 2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;이 예제에서 `{ x: 1 }` 객체는 `addOne()` 함수에 인수로 전달된다. 이 함수는 `obj.x`의 값을 1씩 증가시킨다. `obj`의 값은 객체의 메모리 위치에 대한 참조이므로 `obj.x`에 대한 변경은 함수 외부의 원본 객체에 반영된다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이 동작은 기존 참조에 의한 전달과 다르다는 점에 유의해야 한다. &lt;span style=&quot;background-color: #fdfdfd;&quot;&gt;&amp;nbsp;JavaScript에서 객체의 값(메모리 위치에 대한 참조)은 함수에 값으로 전달되는 반면, &lt;/span&gt;전통적인 참조에 의한 전달에서는 객체의 메모리 위치가 인수로 함수에 전달된다는 차이가 있다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Frontend/JavaScript</category>
      <category>javascript</category>
      <category>Pass by Reference</category>
      <category>Pass by Value</category>
      <author>토리나나</author>
      <guid isPermaLink="true">https://khys.tistory.com/106</guid>
      <comments>https://khys.tistory.com/106#entry106comment</comments>
      <pubDate>Tue, 3 Jan 2023 17:44:45 +0900</pubDate>
    </item>
    <item>
      <title>[JS] 자바스크립트의 깊은 복사와 얕은 복사</title>
      <link>https://khys.tistory.com/105</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; background-color: #ffffff;&quot;&gt;컴퓨터 프로그래밍에서 깊은 복사는 새 메모리 주소로 새 객체를 만들고 원래 객체의 속성 값을 새 객체에 복사하는 객체의 복사본이다. 반면 얕은 복사는 새 메모리 주소로 새 객체를 생성하지만 해당 속성에 대해 원본 객체와 동일한 객체(주소)를 참조하는 객체의 복사본이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; background-color: #ffffff;&quot;&gt;다음은 자바스크립트를 사용하여 깊은 복사와 얕은 복사를 구현한 예제다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1672846351955&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const original = {
  a: 1,
  b: { c: 2 }
};

// original 객체의 얕은 복사본
const shallowCopy = { ...original };

// original 객체의 깊은 복사본
const deepCopy = JSON.parse(JSON.stringify(original));

// original 객체의 속성 수정
original.a = 10;

console.log(original); // { a: 10, b: { c: 2 } }
console.log(shallowCopy); // { a: 1, b: { c: 2 } }
console.log(deepCopy); // { a: 1, b: { c: 2 } }

// original 객체의 중첩된 속성 수정
original.b.c = 20;

console.log(original); // { a: 10, b: { c: 20 } }
console.log(shallowCopy); // { a: 1, b: { c: 20 } }
console.log(deepCopy); // { a: 1, b: { c: 2 } }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; background-color: #ffffff;&quot;&gt;위 예제에서 `original`은 `a`와 `b`라는 두 가지 속성이 있는 객체다. `b` 속성은 그 자체로 속성 `c`를 가진 객체다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;b&gt;`shallowCopy`&lt;/b&gt;는 `&lt;span style=&quot;background-color: #ffffff;&quot;&gt;original` 객체에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;확산(S&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;pread)&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&amp;nbsp;연산자&lt;/span&gt;&lt;span style=&quot;background-color: #f7f7f8;&quot;&gt;(`&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;...`&lt;/span&gt;&lt;span style=&quot;background-color: #f7f7f8;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;를 사용하여 만든&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;얕은 복사본&lt;/b&gt;으로 `&lt;span style=&quot;background-color: #ffffff;&quot;&gt;original`&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;과 동일한 속성 및 값을 가진 새 객체를 포함하지만 속성 `b`에 대한 객체는 `&lt;span style=&quot;background-color: #ffffff;&quot;&gt;original`&lt;/span&gt;과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;같은 주소를 참조하는&amp;nbsp;&lt;/span&gt;동일한 객체이다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;예제에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;`deepCopy`&lt;/b&gt;는 `&lt;span style=&quot;background-color: #ffffff;&quot;&gt;original` 객체&lt;/span&gt;의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;깊은 복사본&lt;/b&gt;으로 `&lt;span style=&quot;background-color: #ffffff;&quot;&gt;original`&lt;/span&gt;과 동일한 속성 및 값을 가진 새 객체를 포함하고 있으며, 속성 `b`에 대한 객체도 `&lt;span style=&quot;background-color: #ffffff;&quot;&gt;original`과&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;동일한 속성 및 값을 가진 새 객체이다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;깊은 복사는 해당 속성이 다른 객체에 대한 참조인 경우에도 모든 속성에 대해 새 객체를 만드는 객체의 복사본이다. 즉, 깊은 복사본은 `&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;original`&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&amp;nbsp;객체와 완전히 독립적이며 복사본을 변경해도 `&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;original`&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;에 영향을 미치지 않는다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;349&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bj2tm0/btrVkcMBhf5/pKabXdiMqtLhmitLTtfCl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bj2tm0/btrVkcMBhf5/pKabXdiMqtLhmitLTtfCl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bj2tm0/btrVkcMBhf5/pKabXdiMqtLhmitLTtfCl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbj2tm0%2FbtrVkcMBhf5%2FpKabXdiMqtLhmitLTtfCl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;512&quot; height=&quot;237&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;349&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;`JSON.parse(JSON.stringify(&lt;span style=&quot;background-color: #ffffff;&quot;&gt;original&lt;/span&gt;))`&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;메서드는 객체의 깊은 복사본을 만드는 편리한 방법이지만 몇 가지 제한 사항이 있다는 점에 유의해야 한다. 이 방식은 단순한 객체에만 작동하며 깊은 복사 함수나 RegExps, Maps, Sets 및 기타 일부 객체 유형에 대해서는 올바르게 작동하지 않는다. 보다 강력한 솔루션을 위해 Lodash(아래 링크 참고)와 같은 라이브러리 또는 이러한 엣지 케이스를 처리할 수 있는 커스텀 함수를 사용해야 할 수도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://lodash.com&quot;&gt;https://lodash.com&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1672846390825&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Lodash&quot; data-og-description=&quot;_.defaults({&amp;nbsp;'a':&amp;nbsp;1&amp;nbsp;},&amp;nbsp;{&amp;nbsp;'a':&amp;nbsp;3,&amp;nbsp;'b':&amp;nbsp;2&amp;nbsp;});_.partition([1,&amp;nbsp;2,&amp;nbsp;3,&amp;nbsp;4],&amp;nbsp;n&amp;nbsp;=&amp;gt;&amp;nbsp;n&amp;nbsp;%&amp;nbsp;2);DownloadLodash is released under the MIT license &amp;amp; supports modern environments. Review the build differences &amp;amp; pick one that&amp;rsquo;s right for you.InstallationIn&quot; data-og-host=&quot;lodash.com&quot; data-og-source-url=&quot;https://lodash.com/&quot; data-og-url=&quot;https://lodash.com/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://lodash.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://lodash.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Lodash&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;_.defaults({&amp;nbsp;'a':&amp;nbsp;1&amp;nbsp;},&amp;nbsp;{&amp;nbsp;'a':&amp;nbsp;3,&amp;nbsp;'b':&amp;nbsp;2&amp;nbsp;});_.partition([1,&amp;nbsp;2,&amp;nbsp;3,&amp;nbsp;4],&amp;nbsp;n&amp;nbsp;=&amp;gt;&amp;nbsp;n&amp;nbsp;%&amp;nbsp;2);DownloadLodash is released under the MIT license &amp;amp; supports modern environments. Review the build differences &amp;amp; pick one that&amp;rsquo;s right for you.InstallationIn&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;lodash.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Frontend/JavaScript</category>
      <category>javascript</category>
      <category>깊은 복사</category>
      <category>얕은 복사</category>
      <author>토리나나</author>
      <guid isPermaLink="true">https://khys.tistory.com/105</guid>
      <comments>https://khys.tistory.com/105#entry105comment</comments>
      <pubDate>Mon, 2 Jan 2023 22:15:57 +0900</pubDate>
    </item>
    <item>
      <title>프로세스(Process)와 스레드(Thread)의 차이 및 개념 정리</title>
      <link>https://khys.tistory.com/104</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;623&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxN20r/btrVpYZ1oTF/wdlBwixzs2t6G4y9Uzn6hK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxN20r/btrVpYZ1oTF/wdlBwixzs2t6G4y9Uzn6hK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxN20r/btrVpYZ1oTF/wdlBwixzs2t6G4y9Uzn6hK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxN20r%2FbtrVpYZ1oTF%2FwdlBwixzs2t6G4y9Uzn6hK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;512&quot; height=&quot;456&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;623&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;컴퓨터 과학에서 프로세스는 실행 중인 프로그램의 인스턴스이고 스레드는 프로세스 내의 별도 실행 경로이다. 프로세스와 스레드는 모두 코드를 실행하고 작업을 수행하는 데 사용되지만 몇 가지 중요한 차이점이 있다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;&lt;b&gt;리소스 소유권 (Resource ownership)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;각 프로세스에는 프로세스가 소유하고 관리하는 메모리, 파일 핸들 및 열린 소켓과 같은 고유한 리소스 세트가 있다. 반면 스레드는 상위 프로세스의 리소스를 공유하는 경량 실행 경로이다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;&lt;b&gt;메모리 관리 (Memory management)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;프로세스에는 다른 프로세스로부터 보호되는 별도의 메모리 공간이 있다. 이는 각 프로세스가 실행 중인 데이터 및 코드의 별도 사본을 가지고 있음을 의미한다. 반면 스레드는 상위 프로세스의 메모리 공간을 공유하므로 상위 프로세스의 데이터에 액세스하고 수정할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;&lt;b&gt;스케줄링 (Scheduling)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;일반적으로 프로세스는 CPU 시간의 공평한 분배를 보장하기 위해 운영 체제에 의해 스케줄링된다. 반면에 스레드는 일반적으로 운영 체제 또는 프로그래밍 언어 런타임에 의해 스케줄링된다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;&lt;b&gt;통신 (Communication)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;프로세스는 일반적으로 파이프, 소켓 또는 공유 메모리와 같은 IPC(inter-process communication: 프로세스 간 통신) 메커니즘을 사용하여 서로 통신한다. 반면 스레드는 프로그래밍 언어 런타임에서 제공하는 공유 메모리 또는 기타 메커니즘을 사용하여 서로 통신할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;전반적으로 프로세스와 스레드는 모두 코드를 실행하고 작업을 수행하는 데 사용되지만 리소스 소유권, 메모리 관리, 스케줄링 및 통신 측면에서 차이가 있다. 프로세스는 일반적으로 별도의 리소스 집합과 다른 프로세스로부터의 보호가 필요한 작업에 사용되는 반면, 스레드는 리소스를 공유하고 병렬 실행의 이점을 얻을 수 있는 작업에 사용된다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>CS</category>
      <category>스레드</category>
      <category>프로세스</category>
      <author>토리나나</author>
      <guid isPermaLink="true">https://khys.tistory.com/104</guid>
      <comments>https://khys.tistory.com/104#entry104comment</comments>
      <pubDate>Sat, 31 Dec 2022 20:12:03 +0900</pubDate>
    </item>
  </channel>
</rss>