---
title: "useApiStream"
description: "SSE 流式请求 composable，复用端点、鉴权与业务码校验，逐条产出分片并支持中止。"
seo_title: "useApiStream"
seo_description: "Consume server-sent events through the movk API layer with endpoint, auth and business-code handling, chunk-by-chunk iteration and abort support."
canonical_url: "https://nuxt.mhaibaraai.cn/docs/composables/use-api-stream"
---
# useApiStream

> SSE 流式请求 composable，复用端点、鉴权与业务码校验，逐条产出分片并支持中止。

## 用法

`useApiStream` 是 [`$api.stream()`](#apistream) 的响应式外壳，返回一个惰性异步生成器，开始迭代才真正发起请求。SSE 的行缓冲、`data:` 多行拼接、注释行与 `\r\n` 全部交给 `eventsource-parser`，解析不了的事件静默跳过——一条心跳或握手行不该中断整条流。

```vue
<script setup lang="ts">
interface ChatChunk {
  content: string
  done: boolean
}

const text = ref('')
const { status, error, stream, abort } = useApiStream<ChatChunk>()

async function start() {
  text.value = ''

  for await (const chunk of stream('/chat', { method: 'POST', body: { message: '你好' } })) {
    text.value += chunk.content
  }
}
</script>

<template>
  <UButton :loading="status === 'streaming'" @click="start">开始</UButton>
  <UButton v-if="status === 'streaming'" @click="abort">中止</UButton>
  <p>{{ text }}</p>
</template>
```

重新发起会自动中止上一条流，组件作用域销毁时同样中止。

## Accept 与 responseType 的处理

> \[\!WARNING\]
> 
> 这两处的默认值都是踩过坑定下来的，改动前请先确认自家网关的行为。

**Accept 默认发通配符，既不是 `application/json` 也不是 `text/event-stream`**。ofetch 见到可 JSON 序列化的 body 会自动塞 `Accept: application/json`——对一条要当流读的请求，这个声明本身就是错的，按 Accept 做内容协商的后端可能据此改走 JSON 渲染器；而主动发 `Accept: text/event-stream` 又会被这类后端（如 DRF）直接拒掉整个请求，实测返回「无法满足 Accept HTTP 头的请求」，连业务逻辑都不进。通配符两头都不踩。需要指定的网关自行传，调用方传了就以调用方为准：

```ts
stream('/chat', { headers: { Accept: 'text/event-stream' } })
```

**不强制 `responseType: 'stream'`**：交给 ofetch 的 content-type 探测。真流（`text/event-stream`）走流；错误响应（`application/json`）仍按 JSON 解析，由拦截器的业务码校验抛出后端文案。有些网关的错误响应是 HTTP 200 + `{ code: 500 }` 信封，强制成流会把它变成一条读不出东西的流。网关把流标成非 SSE 类型时再显式传：

```ts
stream('/chat', { responseType: 'stream' })
```

响应体不是流时抛出可读错误，不会静默空转。

## 错误与中止

- 主动中止（`abort()`、重新发起、作用域销毁）不计入错误态：`status` 置 `'aborted'`，`error` 保持 `null`，迭代静默结束
- 其余错误：写入 `error`、`status` 置 `'error'`、调用 `onError`，然后**继续抛出**，`for await` 外层的 `try/catch` 能接住
- 业务错误（`code` 不在 `successCodes`）在流开始前就由拦截器抛出，toast 与 `movk:api:error` hook 一并走通

```ts
try {
  for await (const chunk of stream('/chat', { onError: e => console.error(e) })) {
    // ...
  }
}
catch (error) {
  // 业务错误、HTTP 错误、流中断
}
```

## $api.stream()

不在组件里、或不需要响应式状态时，直接用 `$api.stream()`：

```ts
const { $api } = useNuxtApp()

for await (const chunk of await $api.stream<ChatChunk>('/chat', { method: 'POST', body })) {
  console.log(chunk.content)
}
```

响应是流时，响应拦截器整段短路——不解包、不校验业务码、不弹成功 toast，`_data` 原样保留，调用方无需再传 `skipUnwrap` / `skipBusinessCheck`。

接 AI SDK 与 `@nuxt/ui` 的 Chat 组件请改用 [`createChatTransport`](/docs/api/chat-transport)。

## API

### useApiStream()

#### Type Parameters

**T** (`unknown`): 单条分片的类型。

#### Returns

**status** (`Ref<ApiStreamStatus>`): 'idle' | 'streaming' | 'success' | 'error' | 'aborted'。开始迭代前为 'idle'。

**error** (`Ref<ApiError | Error | null>`): 错误信息，主动中止不写入。

**stream()** (`(url: string, options?: UseApiStreamOptions<T>) => AsyncGenerator<T>`): 发起流式请求，逐条产出分片。Parameters使用的端点名称，默认走 defaultEndpoint。单条 SSE 事件的 data 解析方式，返回 undefined 丢弃该条。默认容错的 JSON.parse。失败回调，主动中止不触发。其余选项原样交给 ofetch：method、body、headers、query、responseType 等。

**abort()** (`() => void`): 中止当前流，status 切到 'aborted'。

## Changelog

See commit history for [src/runtime/composables/useApiStream.ts](https://github.com/mhaibaraai/movk-nuxt/commits/main/src/runtime/composables/useApiStream.ts).


## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
