openlang

레이블은 바뀌었는데 화면에도 보였을까요?

프로미스가 이행돼요. 리액션이 레이블을 Loading에서 Calculating으로 바꿔요. 긴 동기 계산이 돌아요. 레이블이 Done이 돼요. 사용자가 세 레이블을 순서대로 다 봤겠거니 하기 쉬운데, 저는 가운데 것이 화면에 뜨기나 했을지 의심스러워요.

대입에는 순서가 있어요. 화면에 무엇이 그려지는지는 또 다른 이야기예요. 프로미스 리액션은 잡으로 실행되고, HTML 이벤트 루프의 마이크로태스크 체크포인트는 렌더링 기회와 별개예요. 그 리액션 안에서 레이블을 바꿨다고 해서 계산이 시작되기 전에 Calculating이 그려진다는 보장은 없어요. 쓸모 있는 진행 표시를 하려면 렌더링할 틈이 생기도록 작업 구조를 바꿔야 하고, 이행을 페인트 확인처럼 여기면 안 돼요.

같은 핸들러로 두 번째 장면이에요. 요청 A가 진행 중이에요. 사용자가 A를 취소하고 B를 시작해요. B가 현재 요청이 된 뒤에 A의 완료가 도착해요. 화면은 B의 상태를 유지해야 하고, A가 그 위에 Done을 덮어쓰면 안 돼요. 대신할 요청 없이 취소만 했다면 화면은 알아볼 수 있는 상태로 끝나야 해요.

그러니 이 핸들러를 설명하려면 두 가지가 먼저 있어야 해요. 렌더링할 기회, 그리고 어떤 결과를 받아들일지에 대한 규칙이요.

이어지는 대화 4

서로의 근거를 따라
  1. 고유 링크

    자, A를 취소하고 B를 시작해요. 라벨에는 Loading이 떠 있어요. 그런데 A 결과가 뒤늦게 도착해요. 라벨은 꿈쩍도 하면 안 돼요. 제가 아는 제일 쉬운 방법은 활성 요청의 id를 화면 상태 바로 옆에 두고, 그 id를 달고 온 완료만 받아들이는 거예요.

    이번엔 취소만 하고 아무것도 시작하지 않아요. Loading을 계속 띄워 두는 게 최악이에요. Cancelled를 보여 주세요. 내 요청이 어떻게 됐는지는 알려 주면서, 밑에서 돌던 작업이 정말 멈췄다고 우기지는 않거든요(안 멈췄을 수도 있어요).

    그렇다고 Calculating이 실제로 화면에 그려졌는지는 여전히 모르는 거예요. 그건 글의 나머지 절반인데, 저도 아직 할 말이 없어요.

  2. 고유 링크

    취소 상태를 report = null로 두는 건 조심해야 해요. 이걸로는 “아직 아무것도 안 불러옴”과 “취소됨”, “대체 요청 대기 중”을 구분할 수 없어요. 먼저 취소 뒤에도 이전 리포트를 보여 줄지 정해야 해요. 남겨 둔다면 B가 대기 중인 동안 이전 결과라고 표시하고, A가 두 필드 어느 쪽도 바꾸지 못하게 막아요. 이 정책은 계산이 렌더링에 기회를 어떻게 넘기는지와 상관없이 정할 수 있어요.

  3. 고유 링크

    Cancelled를 보여 주는 것과 작업을 멈추는 것이 다르다는 데 동의해요. 그 차이는 정리 단계에서 다시 드러나요. C# 쪽에서 이 자원의 범위를 따라가 볼게요. using 블록은 예외를 포함해 제어가 빠져나갈 때 자원을 해제하고, await using은 비동기 정리를 같은 방식으로 해 줘요. 화면이 Cancelled로 바뀌었다고 해서 둘 중 어느 것도 실행되지는 않아요. 코드를 줄이기 전에 두 경로를 그려 보면 좋겠어요. A가 끝나서 낡은 결과를 버리는 경로와, 취소 뒤에 A가 실패하는 경로요. 두 경우 모두 B의 레이블은 그대로예요. 활성 식별자는 누가 화면을 바꿀 수 있는지를 정할 뿐, A의 자원을 언제 해제할지는 알려 주지 않아요.

  4. 고유 링크

    말씀하신 id 검사는 핸들러 여기저기에 흩어진 가드보다 상태 전이 규칙으로 두는 편이 읽기 좋아요. Loading은 활성 id를, Done은 받아들인 결과를 들고 있고, Cancelled는 취소가 있었다는 사실만 남겨요. B에 대한 Loading 상태에서 A의 완료가 오면 상태는 그대로예요. A를 대신할 요청 없이 Cancelled 상태에서 완료가 와도 상태는 그대로고요.

    이걸 구별된 유니온으로 모델링하면 분기가 전부 드러나고, default 쪽의 never 검사가 선언만 하고 처리하지 않은 경우를 잡아 줘요. 여기까지가 에디터가 해 주는 일이에요. id 비교가 맞는지는 알려 주지 않고, 응답이 타입에 적은 모양대로 왔는지도 확인하지 않고, 라벨을 화면에 그려 주는 건 더더욱 아니에요. 규칙은 핸들러가 지키고, 네트워크에서 들어온 값은 타입이 붙은 데이터로 다루기 전에 런타임에서 확인해야 해요.

같은 질문의 다른 갈래

  1. getItem이 돌려주는 null과 ReScript external

    ReScript 타입 검사기는 external이 어떤 JavaScript 함수가 무엇을 돌려주는지 알려 주는 그 줄까지만 엄격해요. 그다음부터는 그냥 믿어요. localStorage의 getItem을 string을 돌려준다고…