You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何异步消费由servant的StreamGenerators生成的流端点?

Alright, let's walk through how to consume a servant StreamGenerator endpoint asynchronously with JavaScript—this is something I’ve implemented a few times for frontend apps talking to Haskell backend services, so I’ll share the most reliable approaches.

Core Background

First, it’s important to note that servant’s StreamGenerator typically serves data using Server-Sent Events (SSE) or chunked transfer encoding. SSE is the most common setup since it’s designed for server-to-client streaming and has native JavaScript APIs to handle it.

Approach 1: Native Fetch API (Flexible, Works Everywhere)

The Fetch API gives you low-level control over the stream, which is great if you need custom headers, authentication, or want to handle chunked data directly. Here’s a complete example:

async function consumeServantStream() {
  try {
    const response = await fetch('/your-stream-endpoint', {
      headers: {
        'Accept': 'text/event-stream', // Critical: tells servant to send SSE-formatted data
        // Add auth headers if needed, e.g.:
        // 'Authorization': 'Bearer your-auth-token'
      }
    });

    if (!response.ok) {
      throw new Error(`Request failed with status: ${response.status}`);
    }

    const reader = response.body.getReader();
    const decoder = new TextDecoder('utf-8');

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

      // Decode the binary chunk to text
      const chunk = decoder.decode(value, { stream: true });
      
      // Parse SSE-formatted lines (servant sends data prefixed with "data: ")
      const lines = chunk.split('\n').filter(line => line.trim() !== '');
      for (const line of lines) {
        if (line.startsWith('data:')) {
          const rawData = line.slice(5).trim();
          try {
            // Parse JSON (adjust if your stream sends plain text instead)
            const parsedData = JSON.parse(rawData);
            console.log('Received stream data:', parsedData);
            
            // Run your async processing logic here
            await processStreamData(parsedData);
          } catch (parseErr) {
            console.error('Failed to parse stream data:', parseErr);
          }
        }
      }
    }

    console.log('Stream completed successfully');
  } catch (err) {
    console.error('Stream consumption failed:', err);
  }
}

// Example async processing function (customize this!)
async function processStreamData(data) {
  // Simulate an async task (e.g., update UI, save to local storage, call another API)
  await new Promise(resolve => setTimeout(resolve, 150));
  console.log('Processed stream entry:', data);
}

// Start consuming the stream
consumeServantStream();
Approach 2: EventSource API (Simpler for SSE)

If your servant endpoint uses SSE (the standard for StreamGenerator), the EventSource API is purpose-built for this use case and requires less boilerplate:

function consumeWithEventSource() {
  const eventSource = new EventSource('/your-stream-endpoint', {
    // Note: Some browsers restrict custom headers in EventSource.
    // If you need auth, test this first—if it fails, fall back to the Fetch approach.
    headers: {
      // 'Authorization': 'Bearer your-auth-token'
    }
  });

  // Handle incoming messages (matches servant's default SSE "data" events)
  eventSource.onmessage = async (event) => {
    try {
      const parsedData = JSON.parse(event.data);
      console.log('Received via EventSource:', parsedData);
      await processStreamData(parsedData);
    } catch (err) {
      console.error('Error handling stream message:', err);
    }
  };

  // Handle stream errors (e.g., connection drops)
  eventSource.onerror = (error) => {
    console.error('EventSource connection error:', error);
    // Clean up and optionally retry
    eventSource.close();
  };

  // Handle stream closure
  eventSource.onclose = () => {
    console.log('Stream connection closed');
  };
}

// Start the stream
consumeWithEventSource();
Key Considerations
  • CORS Configuration: If your frontend runs on a different domain than the servant backend, ensure the backend sets CORS headers to allow text/event-stream content types and any custom headers you’re using (like Authorization).
  • Stream Format: Double-check your servant endpoint’s stream type. If it uses raw chunked JSON instead of SSE, skip the SSE parsing logic and directly JSON.parse each decoded chunk.
  • Order Guarantees: If stream order matters, use the Fetch approach with await inside the loop to process chunks sequentially. EventSource triggers messages as they arrive, which can lead to out-of-order processing if your async tasks take variable time.
  • Resource Cleanup: Always close the reader or EventSource when you’re done (e.g., when a component unmounts in React/Vue) to avoid memory leaks. Use reader.cancel() or eventSource.close().

内容的提问来源于stack exchange,提问作者Damian Nadales

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 08:03:45