Skip to content

Commit 0073117

Browse files
committed
refactor(Service): Refactor Service advertise methods to decrease Cyclomatic Complexity and increase readability.
Signed-off-by: Drew Hoener <[email protected]>
1 parent e4c8e90 commit 0073117

File tree

1 file changed

+80
-89
lines changed

1 file changed

+80
-89
lines changed

src/core/Service.ts

Lines changed: 80 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -110,57 +110,20 @@ export default class Service<
110110

111111
this.ros.callOnConnection(call);
112112
}
113+
113114
/**
114-
* Advertise the service. This turns the Service object from a client
115-
* into a server. The callback will be called with every request
116-
* that's made on this service.
117-
*
118-
* @param callback This works similarly to the callback for a C++ service in that you should take care not to overwrite the response object.
119-
* Instead, only modify the values within.
115+
* Common logic for advertising a service
120116
*/
121-
async advertise(
122-
callback: (request: TRequest, response: Partial<TResponse>) => boolean,
117+
#advertiseWithCallback(
118+
callbackWrapper: (bridgeMessage: AnyServiceOp<TRequest, TResponse>) => void,
123119
): Promise<void> {
124-
// Queue this operation to prevent race conditions
125120
this.#operationQueue = this.#operationQueue
126121
.then(() => {
127-
// If already advertised, unadvertise first
128122
if (this.isAdvertised) {
129123
this.#doUnadvertise();
130124
}
131125

132-
// Store the new callback for removal during un-advertisement
133-
this.#serviceCallback = (rosbridgeRequest) => {
134-
if (!isRosbridgeCallServiceMessage<TRequest>(rosbridgeRequest)) {
135-
throw new Error(
136-
`Invalid message received on service channel: ${JSON.stringify(rosbridgeRequest)}`,
137-
);
138-
}
139-
const response = {};
140-
let success: boolean;
141-
try {
142-
success = callback(rosbridgeRequest.args, response);
143-
} catch {
144-
success = false;
145-
}
146-
147-
if (success) {
148-
this.ros.callOnConnection({
149-
op: "service_response",
150-
service: this.name,
151-
values: response,
152-
result: success,
153-
id: rosbridgeRequest.id,
154-
} satisfies RosbridgeServiceResponseMessage<Partial<TResponse>>);
155-
} else {
156-
this.ros.callOnConnection({
157-
op: "service_response",
158-
service: this.name,
159-
result: success,
160-
id: rosbridgeRequest.id,
161-
} satisfies RosbridgeServiceResponseMessage<Partial<TResponse>>);
162-
}
163-
};
126+
this.#serviceCallback = callbackWrapper;
164127

165128
this.ros.on(this.name, this.#serviceCallback);
166129
this.ros.callOnConnection({
@@ -178,6 +141,53 @@ export default class Service<
178141
return this.#operationQueue;
179142
}
180143

144+
/**
145+
* Advertise the service. This turns the Service object from a client
146+
* into a server. The callback will be called with every request
147+
* that's made on this service.
148+
*
149+
* @param callback This works similarly to the callback for a C++ service in that you should take care not to overwrite the response object.
150+
* Instead, only modify the values within.
151+
*/
152+
advertise(
153+
callback: (request: TRequest, response: Partial<TResponse>) => boolean,
154+
): Promise<void> {
155+
return this.#advertiseWithCallback((bridgeMessage) => {
156+
if (bridgeMessage.op !== "call_service") {
157+
throw new Error(
158+
`Invalid message received on service channel: ${JSON.stringify(bridgeMessage)}`,
159+
);
160+
}
161+
162+
const request = bridgeMessage as IncomingCallServiceOp<TRequest>;
163+
const response: Partial<TResponse> = {};
164+
165+
let success: boolean;
166+
try {
167+
success = callback(request.args, response);
168+
} catch {
169+
success = false;
170+
}
171+
172+
if (success) {
173+
this.ros.callOnConnection({
174+
op: "service_response",
175+
service: this.name,
176+
values: response,
177+
result: success,
178+
id: request.id,
179+
});
180+
} else {
181+
this.ros.callOnConnection({
182+
op: "service_response",
183+
service: this.name,
184+
result: success,
185+
id: request.id,
186+
});
187+
}
188+
});
189+
}
190+
181191
/**
182192
* Internal method to perform unadvertisement without queueing
183193
*/
@@ -233,56 +243,37 @@ export default class Service<
233243
* An alternate form of Service advertisement that supports a modern Promise-based interface for use with async/await.
234244
* @param callback An asynchronous callback processing the request and returning a response.
235245
*/
236-
async advertiseAsync(
246+
advertiseAsync(
237247
callback: (request: TRequest) => Promise<TResponse>,
238248
): Promise<void> {
239-
// Queue this operation to prevent race conditions
240-
this.#operationQueue = this.#operationQueue
241-
.then(() => {
242-
// If already advertised, unadvertise first
243-
if (this.isAdvertised) {
244-
this.#doUnadvertise();
245-
}
249+
return this.#advertiseWithCallback((bridgeMessage) => {
250+
if (bridgeMessage.op !== "call_service") {
251+
throw new Error(
252+
`Invalid message received on service channel: ${JSON.stringify(bridgeMessage)}`,
253+
);
254+
}
246255

247-
this.#serviceCallback = (rosbridgeRequest) => {
248-
if (!isRosbridgeCallServiceMessage<TRequest>(rosbridgeRequest)) {
249-
throw new Error(
250-
`Invalid message received on service channel: ${JSON.stringify(rosbridgeRequest)}`,
251-
);
252-
}
253-
(async () => {
254-
try {
255-
this.ros.callOnConnection({
256-
op: "service_response",
257-
service: this.name,
258-
result: true,
259-
values: await callback(rosbridgeRequest.args),
260-
id: rosbridgeRequest.id,
261-
} satisfies RosbridgeServiceResponseMessage<TResponse>);
262-
} catch (err) {
263-
this.ros.callOnConnection({
264-
op: "service_response",
265-
service: this.name,
266-
result: false,
267-
values: String(err),
268-
id: rosbridgeRequest.id,
269-
} satisfies RosbridgeServiceResponseMessage<TResponse>);
270-
}
271-
})().catch(console.error);
272-
};
273-
this.ros.on(this.name, this.#serviceCallback);
274-
this.ros.callOnConnection({
275-
op: "advertise_service",
276-
type: this.serviceType,
277-
service: this.name,
278-
});
279-
this.isAdvertised = true;
280-
})
281-
.catch((err: unknown) => {
282-
this.emit("error", err);
283-
throw err;
284-
});
256+
const request = bridgeMessage as IncomingCallServiceOp<TRequest>;
285257

286-
return this.#operationQueue;
258+
(async () => {
259+
try {
260+
this.ros.callOnConnection({
261+
op: "service_response",
262+
service: this.name,
263+
result: true,
264+
values: await callback(request.args),
265+
id: request.id,
266+
});
267+
} catch (err) {
268+
this.ros.callOnConnection({
269+
op: "service_response",
270+
service: this.name,
271+
result: false,
272+
values: String(err),
273+
id: request.id,
274+
});
275+
}
276+
})().catch(console.error);
277+
});
287278
}
288279
}

0 commit comments

Comments
 (0)