> ## Documentation Index
> Fetch the complete documentation index at: https://make.edicus.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 닫기와 저장 후처리

> 저장 반영이 끝난 뒤에 안전하게 편집기를 닫는 방법

편집기를 닫는 일은 `session.close()` 한 줄이면 됩니다. 문제는 **언제** 닫느냐입니다. 저장 결과를 내 서버에 반영하고 계신다면 그 작업이 끝나기 전에 닫으면 반영이 유실됩니다.

## 무엇이 문제인가

사용자가 편집기 안에서 나가기나 장바구니 이동을 누르면 편집기는 메시지를 두 번 보냅니다.

1. `save-doc-report` (`status: 'end'`)
2. `close` 또는 `goto-cart`

호스트가 1번 핸들러에서 썸네일이나 `docInfo`를 자기 서버에 반영한다면, 그것은 네트워크 호출이라 2번이 도착한 시점에 아직 진행 중일 수 있습니다. **이벤트 핸들러 안의 `await`는 다음 이벤트의 처리를 막아 주지 않습니다.** 그래서 아래 코드는 틀렸습니다.

```ts theme={null}
// 틀린 코드
session.on('save-doc-report', async (report) => {
  if (report.status === 'end') await persistThumbnail(report.docInfo)
})
session.on('close', () => {
  session.close()
  navigateAway() // persistThumbnail()이 아직 끝나지 않았을 수 있습니다
})
```

## `waitForExit()`

세션은 핸들러가 반환한 `Promise`를 추적합니다. `waitForExit()`는 종료 요청이 도착한 시점에 진행 중이던 핸들러 Promise가 전부 settle된 뒤에 resolve합니다.

```ts theme={null}
session.on('save-doc-report', async (report) => {
  if (report.status !== 'end') return
  await persistThumbnail(report.docInfo) // 세션이 이 Promise를 추적합니다
})

const reason = await session.waitForExit() // persistThumbnail()이 끝난 뒤 'close' | 'goto-cart'
session.close()
navigateAway(reason)
```

핸들러가 `async`여서 `Promise`를 반환하기만 하면 됩니다. 따로 등록할 것이 없습니다.

## 동작 규칙

<Steps>
  <Step title="스냅샷">
    종료 요청이 도착한 순간에 진행 중이던 Promise만 기다립니다. 정확히는 그 메시지의 핸들러까지 실행된 직후를 기준으로 삼습니다. 그 뒤에 시작된 작업은 기다리지 않습니다.
  </Step>

  <Step title="에러 내성">
    추적 중인 핸들러가 reject되어도 종료를 막지 않습니다(`Promise.allSettled`). 그 reject는 세션이 소비하고 `console.error`로 남기므로 조용히 사라지지는 않습니다. 사용자 안내 같은 실제 에러 처리는 핸들러 안에서 하세요.
  </Step>

  <Step title="타임아웃">
    `settleTimeoutMs`(기본 10000ms)를 넘기면 `console.warn`을 남기고 그대로 resolve합니다. 사용자가 편집기에 갇히는 것보다 뒤처리 유실이 낫다는 판단입니다. 무제한으로 기다리려면 `Infinity`, 아예 기다리지 않으려면 `0`을 주세요.
  </Step>

  <Step title="세션이 먼저 닫히면">
    `waitFor`와 마찬가지로 reject합니다.
  </Step>
</Steps>

```ts theme={null}
const reason = await session.waitForExit({ settleTimeoutMs: 30_000 })
```

<Note>
  `waitForExit()`는 iframe을 제거하지 않습니다. 종료 시점을 알려줄 뿐이고, 실제 정리는 `session.close()`로 하셔야 합니다.
</Note>

## 어떤 이벤트를 기다리나

| 세션 | 기다리는 action |
| - | - |
| `EditorSession` | `close` 또는 `goto-cart` |
| `TnViewSession` | `close` |
| `PreviewSession` | `close` |

resolve되는 값이 그 action 이름입니다. 장바구니로 보낼지 원래 화면으로 돌아갈지 이 값으로 분기하시면 됩니다.

```ts theme={null}
const reason = await session.waitForExit()
session.close()
if (reason === 'goto-cart') router.push('/cart')
else router.back()
```

## `no_update`를 쓸 때

띄우기 옵션에 `no_update: true`를 주면 저장 버튼이 **나가기**로 바뀝니다. 누르면 저장하지 않고 바로 `goto-cart`를 보냅니다. 이 경우 `save-doc-report`가 오지 않으므로 저장 후처리도 없습니다.

## 핸들러 안에서 닫을 때 주의

```ts theme={null}
// onAny 기록이 누락됩니다
session.onAny((e) => log(e))
session.on('goto-cart', () => session.close())
```

dispatch는 `on`과 `once` 핸들러를 먼저 실행하고 그다음 `onAny`를 실행합니다. `on('goto-cart')` 안에서 `close()`를 부르면 그 자리에서 모든 핸들러가 해제되므로 같은 메시지에 대한 `onAny`가 실행되지 않습니다. `waitForExit()`를 쓰면 이 문제도 함께 해결됩니다.

## 다음 단계

<Columns cols={2}>
  <Card title="주문" icon="cart-shopping" href="/docs/server/orders">
    편집이 끝난 프로젝트를 주문으로 넘깁니다.
  </Card>

  <Card title="편집기 이벤트" icon="arrow-left" href="/docs/sdk/events">
    `save-doc-report`의 `docInfo` 전체 필드를 봅니다.
  </Card>
</Columns>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.