Skip to content

Commit 58b0df1

Browse files
docs: use async functions in gRPC streaming examples
Streaming event handlers can only fire when the call stack is empty, so synchronous default functions prevent them from running. Update the server and client streaming examples to use async/await with Promises and add a short explanation of why async is required. Applies to next/ and v1.6.x. Made-with: Cursor
1 parent 9a9de69 commit 58b0df1

File tree

2 files changed

+158
-138
lines changed
  • docs/sources/k6

2 files changed

+158
-138
lines changed

docs/sources/k6/next/using-k6/protocols/grpc.md

Lines changed: 79 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -85,79 +85,90 @@ export default () => {
8585

8686
{{< /code >}}
8787

88+
### Streaming requires async functions
89+
90+
gRPC streaming relies on event handlers (`stream.on`) to process messages.
91+
In JavaScript, event handlers can only run when the call stack is empty.
92+
If your default function is synchronous, event handlers can only fire after it returns.
93+
94+
To let event handlers run during test execution, declare the default function as `async` and use a `Promise` to wait for the stream to complete.
95+
All of the streaming examples on this page follow this pattern.
96+
8897
### Server gRPC streaming
8998

9099
In server streaming mode, the client sends a single request to the server, and the server replies with multiple responses.
91100

92-
The example below demonstrates server streaming.
101+
The following example demonstrates server streaming:
93102

94103
{{< code >}}
95104

96105
```javascript
97106
import { Client, Stream } from 'k6/net/grpc';
98-
import { sleep } from 'k6';
99107

100108
const COORD_FACTOR = 1e7;
101109

102110
const client = new Client();
103111

104-
export default () => {
112+
export default async function () {
105113
if (__ITER == 0) {
106114
client.connect('127.0.0.1:10000', { plaintext: true, reflect: true });
107115
}
108116

109117
const stream = new Stream(client, 'main.FeatureExplorer/ListFeatures', null);
110118

111-
stream.on('data', function (feature) {
112-
console.log(
113-
`Found feature called "${feature.name}" at ${feature.location.latitude / COORD_FACTOR}, ${
114-
feature.location.longitude / COORD_FACTOR
115-
}`
116-
);
117-
});
118-
119-
stream.on('end', function () {
120-
// The server has finished sending
121-
client.close();
122-
console.log('All done');
119+
await new Promise((resolve, reject) => {
120+
stream.on('data', function (feature) {
121+
console.log(
122+
`Found feature called "${feature.name}" at ${feature.location.latitude / COORD_FACTOR}, ${
123+
feature.location.longitude / COORD_FACTOR
124+
}`
125+
);
126+
});
127+
128+
stream.on('error', function (e) {
129+
reject(e);
130+
});
131+
132+
stream.on('end', function () {
133+
client.close();
134+
console.log('All done');
135+
resolve();
136+
});
137+
138+
stream.write({
139+
lo: {
140+
latitude: 400000000,
141+
longitude: -750000000,
142+
},
143+
hi: {
144+
latitude: 420000000,
145+
longitude: -730000000,
146+
},
147+
});
123148
});
124-
125-
// send a message to the server
126-
stream.write({
127-
lo: {
128-
latitude: 400000000,
129-
longitude: -750000000,
130-
},
131-
hi: {
132-
latitude: 420000000,
133-
longitude: -730000000,
134-
},
135-
});
136-
137-
sleep(0.5);
138-
};
149+
}
139150
```
140151

141152
{{< /code >}}
142153

143-
In the example script, k6 connects to a gRPC server, creates a stream, and sends a message to the server with latitude and longitude coordinates. When the server sends data back, it logs the feature name and its location. When the server finishes sending data, it closes the client connection and logs a completion message.
154+
In this example, k6 connects to a gRPC server, creates a stream, and sends a message with latitude and longitude coordinates.
155+
The `async` default function wraps the stream logic in a `Promise`, which lets event handlers fire while the function awaits.
156+
When the server finishes sending data, the `end` handler closes the client and resolves the promise.
144157

145158
### Client gRPC streaming
146159

147160
The client streaming mode is the opposite of the server streaming mode. The client sends multiple requests to the server, and the server replies with a single response.
148161

149-
The example below demonstrates client streaming.
162+
The following example demonstrates client streaming:
150163

151164
{{< code >}}
152165

153166
```javascript
154167
import { Client, Stream } from 'k6/net/grpc';
155-
import { sleep } from 'k6';
156168

157169
const COORD_FACTOR = 1e7;
158170
const client = new Client();
159171

160-
// a sample points collection
161172
const points = [
162173
{
163174
location: { latitude: 407838351, longitude: -746143763 },
@@ -181,52 +192,51 @@ const points = [
181192
},
182193
];
183194

184-
export default () => {
195+
export default async function () {
185196
if (__ITER == 0) {
186197
client.connect('127.0.0.1:10000', { plaintext: true, reflect: true });
187198
}
188199

189200
const stream = new Stream(client, 'main.RouteGuide/RecordRoute');
190201

191-
stream.on('data', (stats) => {
192-
console.log(`Finished trip with ${stats.pointCount} points`);
193-
console.log(`Passed ${stats.featureCount} features`);
194-
console.log(`Travelled ${stats.distance} meters`);
195-
console.log(`It took ${stats.elapsedTime} seconds`);
202+
await new Promise((resolve, reject) => {
203+
stream.on('data', (stats) => {
204+
console.log(`Finished trip with ${stats.pointCount} points`);
205+
console.log(`Passed ${stats.featureCount} features`);
206+
console.log(`Travelled ${stats.distance} meters`);
207+
console.log(`It took ${stats.elapsedTime} seconds`);
208+
});
209+
210+
stream.on('error', (e) => {
211+
reject(e);
212+
});
213+
214+
stream.on('end', () => {
215+
client.close();
216+
console.log('All done');
217+
resolve();
218+
});
219+
220+
for (let i = 0; i < 3; i++) {
221+
const point = points[Math.floor(Math.random() * points.length)];
222+
console.log(
223+
`Visiting point ${point.name} ${point.location.latitude / COORD_FACTOR}, ${
224+
point.location.longitude / COORD_FACTOR
225+
}`
226+
);
227+
stream.write(point.location);
228+
}
229+
230+
stream.end();
196231
});
197-
198-
stream.on('end', () => {
199-
client.close();
200-
console.log('All done');
201-
});
202-
203-
// send 3 random points
204-
for (let i = 0; i < 3; i++) {
205-
const point = points[Math.floor(Math.random() * points.length)];
206-
pointSender(stream, point);
207-
}
208-
209-
// close the client stream
210-
stream.end();
211-
};
212-
213-
const pointSender = (stream, point) => {
214-
console.log(
215-
`Visiting point ${point.name} ${point.location.latitude / COORD_FACTOR}, ${
216-
point.location.longitude / COORD_FACTOR
217-
}`
218-
);
219-
220-
// send the location to the server
221-
stream.write(point.location);
222-
223-
sleep(0.5);
224-
};
232+
}
225233
```
226234

227235
{{< /code >}}
228236

229-
In the example script, k6 establishes a connection to a gRPC server, creates a stream, and sends three random points. The server responds with statistics about the trip, which are logged to the console. The code also handles the end of the stream, closing the client and logging a completion message.
237+
In this example, k6 connects to a gRPC server, creates a stream, and sends three random points.
238+
The code wraps the stream logic in a `Promise`, the same pattern as the server streaming example.
239+
The server responds with trip statistics, and the `end` handler closes the client and resolves the promise.
230240

231241
### Bidirectional gRPC streaming
232242

0 commit comments

Comments
 (0)