Skip to content

AbortController 取消请求

停止生成、超时控制

5.1 基础用法

javascript
const controller = new AbortController();

// 传入 signal
const response = await fetch('/api/chat', {
    method: 'POST',
    signal: controller.signal,
    body: JSON.stringify({ prompt: 'Hello' })
});

// 用户点击 "停止生成"
document.getElementById('stop').onclick = () => {
    controller.abort();
};

5.2 完整 ChatStream 类

javascript
class ChatStream {
    constructor() {
        this.controller = null;
    }

    async stream(messages, onToken) {
        // 取消之前的请求
        this.abort();

        this.controller = new AbortController();
        const signal = this.controller.signal;

        try {
            const response = await fetch('/api/chat', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ messages }),
                signal
            });

            const reader = response.body.getReader();
            const decoder = new TextDecoder();
            let buffer = '';

            while (true) {
                if (signal.aborted) break;

                const { done, value } = await reader.read();
                if (done) break;

                buffer += decoder.decode(value, { stream: true });
                const lines = buffer.split('\n');
                buffer = lines.pop() || '';

                for (const line of lines) {
                    if (line.startsWith('data: ')) {
                        const data = line.slice(6);
                        if (data === '[DONE]') return;

                        const json = JSON.parse(data);
                        const content = json.choices?.[0]?.delta?.content;
                        if (content) onToken(content);
                    }
                }
            }
        } catch (error) {
            if (error.name === 'AbortError') {
                console.log('Stream aborted by user');
            } else {
                throw error;
            }
        } finally {
            this.controller = null;
        }
    }

    abort() {
        this.controller?.abort();
    }
}

// 使用
const chat = new ChatStream();

document.getElementById('send').onclick = () => {
    const output = document.getElementById('output');
    output.textContent = '';

    chat.stream(
        [{ role: 'user', content: 'Hello' }],
        (token) => { output.textContent += token; }
    );
};

document.getElementById('stop').onclick = () => {
    chat.abort();
};

5.3 超时控制

javascript
// 方法 1: AbortSignal.timeout() (现代浏览器)
const response = await fetch(url, {
    signal: AbortSignal.timeout(30000) // 30秒超时
});

// 方法 2: 手动实现
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000);

try {
    const response = await fetch(url, { signal: controller.signal });
    // ...
} finally {
    clearTimeout(timeoutId);
}

// 方法 3: 合并多个 signal
const userController = new AbortController();
const timeoutSignal = AbortSignal.timeout(30000);

const response = await fetch(url, {
    signal: AbortSignal.any([userController.signal, timeoutSignal])
});

前端面试知识库