Streaming and Waiting
By the Syncfusion DevTutorials team · Last updated
Replace a 14-second spinner with an answer that shows each step, streams as it is written, and has a Stop button that really cancels the work.
Why AI Waiting Feels So Long
Perceived wait = how long a wait feels to the user, not how long it really is.
In this lesson, you will turn a 14-second spinner into an answer that shows each step, streams as it is written, and can be stopped.
A support agent asks the AI: "Where is my parcel for order #A-1042?"
The agent behind the app works hard. It looks up the order. It checks the courier. Then it writes the answer.
But the screen shows only a spinner. For 14 seconds.
Press Play, or use Next to go one step at a time.
- Frames 1 to 3: the right side shows the real work. The left side shows only "Loading…".
- The gauge: the seconds of waiting with no clue about what is happening.
- Frame 4: the whole answer appears at once, at the very end.
Simple lesson: a spinner hides real work. Users cannot tell busy from broken.
A Real-World Example
Think about ordering food in an app.
- App A shows "Placing order…" for 25 minutes. Then the food arrives.
- App B shows "Order received", then "Cooking", then "Driver on the way", with a map.
Both took 25 minutes. App B felt faster, and you never wondered if it had failed.
AI answers work the same way:
- "Order received" = the first sign of progress, within a second.
- "Cooking", "Driver on the way" = the agent's tool steps.
- The map = the answer streaming in as it is written.
Click the button. The Build tab plays the scripted agent through the naive interface.
- The screen: what the user sees at each moment.
- Events from the agent: what the agent was really doing at that moment.
- Checks for this scenario: the rules this interface breaks.
Simple lesson: the agent's work is already there. The interface just has to show it.
What You Will Learn
In this lesson, you will learn:
- Why AI waiting is different from normal loading.
- How to show progress from the first second.
- How streaming works, and where it cannot help.
- How to build a Stop button that really stops.
- The checks your interface must pass.
About the Build tab
- The Build tab in the AI UX Lab plays a scripted agent. Its events (tool calls, words, errors) come with fixed timings, so every run is the same.
- Your interface is a set of choices in the lab, so the checks can be exact. In a real app, the same rules are checked on the page, for example with Playwright.
- The Before (naive) button shows the starting interface. My interface shows your changes.
Where the 14 Seconds Go
Two Kinds of Waiting
An AI answer has two parts:
- Tool time. The agent calls tools: it looks up the order, then checks the courier. No words exist yet.
- Writing time. The model writes the answer, a few words at a time.
Token = a small piece of text the model writes, about one short word.
In this scenario, tool time is about 9 seconds. Writing time is about 5.
What Each Interface Shows
Watch the same 14-second answer on three interfaces.
Step through all four frames.
- Frames 1 and 2: only "stream + steps" shows anything. No words exist yet, so streaming alone cannot help.
- Frame 3: words arrive. Now streaming helps too.
- Frame 4: when each interface first shows something useful.
Easy way to remember: steps cover the tool time. Streaming covers the writing time. You need both.
Simple lesson: show something useful within one second, every time.
Show Progress From the First Second
Tool Steps
Tool step = one small chip that says what the agent is doing, such as "Checking the courier".
Show the chip the moment the tool starts. Tick it when the tool finishes.
This tells the user the AI is working, and on what. It also explains a slow answer: "Oh, the courier check is slow."
Skeleton
Skeleton = grey bars where the answer will appear.
It shows the shape of the answer before the words arrive. Use it with tool steps, not instead of them.
Streaming
Streaming = showing each token on screen as soon as it arrives, instead of waiting for the whole answer.
In React, you read the response bit by bit and add each token to the text:
const reader = res.body!.pipeThrough(new TextDecoderStream()).getReader();
for (;;) {
const { value, done } = await reader.read();
if (done) break;
for (const line of value.split("\n").filter(Boolean)) {
const ev = JSON.parse(line);
if (ev.type === "tool-start") setSteps((s) => [...s, { label: ev.label, done: false }]);
if (ev.type === "token") setText((t) => t + ev.text);
}
}
Each line from the server is one event. The UI reacts to each one as it comes.
Simple lesson: tool steps for the tool time, streaming for the writing time, a skeleton for the shape.
A Stop Button That Really Stops
Hiding Is Not Stopping
A Stop button that only hides the answer is a trap. The request keeps running on the server. When it finishes, it can still save, send or charge.
Step through both frames.
- Frame 1: at 5 seconds, the user presses Stop on both sides.
- Frame 2: on the left, the work finished anyway and saved the answer to the ticket. On the right, nothing happened.
Cancel the Request
In the browser, an AbortController cancels the request itself:
const abort = useRef<AbortController | null>(null);
async function ask(question: string) {
abort.current = new AbortController();
await fetch("/api/ask", { method: "POST", body: question, signal: abort.current.signal });
}
const stop = () => abort.current?.abort();
abort() closes the connection. Your server should notice and stop the agent, so no later step runs.
Check the server side too.
- Real tools differ in what happens after the browser disconnects.
- Some keep the agent running unless you pass the cancel on.
- Test it with your own stack: press Stop, then check that nothing was saved.
Simple lesson: Stop must cancel the work, not just the screen.
Fix the 14-Second Wait
The Four Choices
In the Build tab, Lesson 2 has four choices:
| Choice | Values | What it does |
|---|---|---|
| Show the answer | spinner or tokens | Wait for the whole answer, or stream it |
| Show each step | yes or no | A chip per tool step, ticked when done |
| Skeleton | yes or no | Grey bars where the answer will go |
| Stop button | yes or no | Cancels the request, so nothing runs later |
The Checks
Your interface must pass three scenarios:
- A 14-second answer: first words within 1 s of the first token, and each tool step shown when it starts.
- A quick answer: first words within 1 s.
- The user gives up: Stop cancels the work, with no side effects later.
Lab Exercise: Fix the Wait
Your task: change the Lesson 2 choices in the Build tab until every scenario passes. Then press Check all scenarios.
Click the button and open the Build tab.
- The choices: at the top left, under "My interface".
- Play: watch your interface handle the scenario, moment by moment.
- Check all scenarios: runs all three. A failing check says which moment went wrong.
Simple lesson: show steps from the first second, stream the words, and make Stop real.
What Changes per Framework
| Option | Streaming and Stop |
|---|---|
| Hand-built React | fetch with a stream reader and an AbortController, as above |
| CopilotKit | Streams messages and tool calls to the UI over AG-UI |
| Vercel AI SDK UI | useChat streams the reply and gives a stop() function |
| Syncfusion AI components | AI AssistView: a ready-made panel for AI replies (optional, not graded) |
Build It in React
The Build tab uses switches. Real apps use code. The lab's Code tab gives you a React component and a scripted agent. Your component listens to the agent's events and draws the page. The checks then look at the real page, the way a Playwright test would.
The key move is to add each event to the screen the moment it arrives:
useEffect(() => agent.on((e) => {
if (e.type === "tool-start") setSteps((s) => [...s, e]);
if (e.type === "token") setText((t) => t + e.text);
}), [agent]);
The Stop button must call agent.cancel(). Hiding the answer is not enough.
TL;DR
- Perceived wait = how long a wait feels. A spinner makes it feel longer.
- Two kinds of waiting = tool time (no words yet) and writing time.
- Tool steps = chips that show what the agent is doing. They cover the tool time.
- Streaming = show tokens as they arrive. It covers the writing time.
- Skeleton = the shape of the answer before the words.
- Stop = abort the request, so nothing runs later.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Streaming only | "Streaming fixes slow answers." | Add tool steps for the time before the first word. | The screen is blank for 9 seconds while tools run. |
| A Stop that only hides | "The user pressed Stop, so it stopped." | Abort the request and the agent. | The answer still saves or sends after Stop. |
| A skeleton with no steps | "Grey bars show progress." | Use steps to say what is happening. | A skeleton for 14 seconds looks frozen too. |
| Updating on every token with heavy work | "Re-render everything each time." | Append the text; keep each update small. | The page stutters while the answer streams. |
| Hiding which step is slow | "Users don't care about tools." | Show the step that is running. | Users blame the AI for a slow courier system. |
Check Your Understanding
Exercises
What to Remember
- A spinner hides real work. Users cannot tell busy from broken.
- Show something useful within one second.
- Tool steps cover the tool time. Streaming covers the writing time.
- A skeleton shows the shape, but does not say what is happening.
- Stop must cancel the request, not just hide the answer.
- Test Stop on your own stack: nothing should save or send after it.
Further Reading
- Response times: the 3 important limits, by Nielsen Norman Group: why 1 second and 10 seconds matter.
- AbortController, on MDN: how to cancel a request in the browser.
Next: Sources and Doubt, where a confident wrong answer gets citation chips, a "not sure" state and a scope note.