Skip to content

Commit c5d1039

Browse files
authored
[core-lro] add a flag to control whether to throw when the operation is unsuccessful (Azure#23311)
Adds `resolveOnUnsuccessful` flag that controls whether to throw an error if the operation failed or was canceled. This flag is turned off by default but REST-level clients should turn it on to maintain the direction of not raising exceptions when receiving unexpected responses.
1 parent cece4ea commit c5d1039

File tree

16 files changed

+338
-165
lines changed

16 files changed

+338
-165
lines changed

sdk/core/core-lro/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# Release History
22

3-
## 2.3.2 (Unreleased)
3+
## 2.4.0 (Unreleased)
44

55
### Features Added
66

7+
- Add `resolveOnUnsuccessful` to `CreatePollerOptions` and `LroEngineOptions` to control whether to throw an error if the operation failed or was canceled.
8+
79
### Breaking Changes
810

911
### Bugs Fixed

sdk/core/core-lro/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@azure/core-lro",
33
"author": "Microsoft Corporation",
44
"sdk-type": "client",
5-
"version": "2.3.2",
5+
"version": "2.4.0",
66
"description": "Isomorphic client library for supporting long-running operations in node.js and browser.",
77
"tags": [
88
"isomorphic",

sdk/core/core-lro/review/core-lro.api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export function createHttpPoller<TResult, TState extends OperationState<TResult>
1616
export interface CreateHttpPollerOptions<TResult, TState> {
1717
intervalInMs?: number;
1818
processResult?: (result: unknown, state: TState) => TResult;
19+
resolveOnUnsuccessful?: boolean;
1920
resourceLocationConfig?: LroResourceLocationConfig;
2021
restoreFrom?: string;
2122
updateState?: (state: TState, response: LroResponse) => void;
@@ -44,6 +45,7 @@ export interface LroEngineOptions<TResult, TState> {
4445
isDone?: (lastResponse: unknown, state: TState) => boolean;
4546
lroResourceLocationConfig?: LroResourceLocationConfig;
4647
processResult?: (result: unknown, state: TState) => TResult;
48+
resolveOnUnsuccessful?: boolean;
4749
resumeFrom?: string;
4850
updateState?: (state: TState, lastResponse: RawResponse) => void;
4951
}
@@ -86,6 +88,7 @@ export abstract class Poller<TState extends PollOperationState<TResult>, TResult
8688
pollUntilDone(pollOptions?: {
8789
abortSignal?: AbortSignalLike;
8890
}): Promise<TResult>;
91+
protected resolveOnUnsuccessful: boolean;
8992
stopPolling(): void;
9093
toString(): string;
9194
}

sdk/core/core-lro/src/http/models.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,8 @@ export interface CreateHttpPollerOptions<TResult, TState> {
105105
* service.
106106
*/
107107
withOperationLocation?: (operationLocation: string) => void;
108+
/**
109+
* Control whether to throw an exception if the operation failed or was canceled.
110+
*/
111+
resolveOnUnsuccessful?: boolean;
108112
}

sdk/core/core-lro/src/http/operation.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,9 +208,10 @@ export async function initHttpOperation<TResult, TState>(inputs: {
208208
stateProxy: StateProxy<TState, TResult>;
209209
resourceLocationConfig?: LroResourceLocationConfig;
210210
processResult?: (result: unknown, state: TState) => TResult;
211+
setErrorAsResult: boolean;
211212
lro: LongRunningOperation;
212213
}): Promise<RestorableOperationState<TState>> {
213-
const { stateProxy, resourceLocationConfig, processResult, lro } = inputs;
214+
const { stateProxy, resourceLocationConfig, processResult, lro, setErrorAsResult } = inputs;
214215
return initOperation({
215216
init: async () => {
216217
const response = await lro.sendInitialRequest();
@@ -232,6 +233,7 @@ export async function initHttpOperation<TResult, TState>(inputs: {
232233
? ({ flatResponse }, state) => processResult(flatResponse, state)
233234
: ({ flatResponse }) => flatResponse as TResult,
234235
getOperationStatus: getStatusFromInitialResponse,
236+
setErrorAsResult,
235237
});
236238
}
237239

@@ -300,8 +302,18 @@ export async function pollHttpOperation<TState, TResult>(inputs: {
300302
setDelay: (intervalInMs: number) => void;
301303
options?: { abortSignal?: AbortSignalLike };
302304
state: RestorableOperationState<TState>;
305+
setErrorAsResult: boolean;
303306
}): Promise<void> {
304-
const { lro, stateProxy, options, processResult, updateState, setDelay, state } = inputs;
307+
const {
308+
lro,
309+
stateProxy,
310+
options,
311+
processResult,
312+
updateState,
313+
setDelay,
314+
state,
315+
setErrorAsResult,
316+
} = inputs;
305317
return pollOperation({
306318
state,
307319
stateProxy,
@@ -320,5 +332,6 @@ export async function pollHttpOperation<TState, TResult>(inputs: {
320332
* references an inner this, so we need to preserve a reference to it.
321333
*/
322334
poll: async (location, inputOptions) => lro.sendPollRequest(location, inputOptions),
335+
setErrorAsResult,
323336
});
324337
}

sdk/core/core-lro/src/http/poller.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@ export async function createHttpPoller<TResult, TState extends OperationState<TR
3131
restoreFrom,
3232
updateState,
3333
withOperationLocation,
34+
resolveOnUnsuccessful = false,
3435
} = options || {};
3536
return buildCreatePoller<LroResponse, TResult, TState>({
3637
getStatusFromInitialResponse,
3738
getStatusFromPollResponse: getOperationStatus,
3839
getOperationLocation,
3940
getResourceLocation,
4041
getPollingInterval: parseRetryAfter,
42+
resolveOnUnsuccessful,
4143
})(
4244
{
4345
init: async () => {

sdk/core/core-lro/src/legacy/lroEngine/lroEngine.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,29 @@ export class LroEngine<TResult, TState extends PollOperationState<TResult>> exte
2020
private config: PollerConfig;
2121

2222
constructor(lro: LongRunningOperation<TResult>, options?: LroEngineOptions<TResult, TState>) {
23-
const { intervalInMs = POLL_INTERVAL_IN_MS, resumeFrom } = options || {};
23+
const {
24+
intervalInMs = POLL_INTERVAL_IN_MS,
25+
resumeFrom,
26+
resolveOnUnsuccessful = false,
27+
isDone,
28+
lroResourceLocationConfig,
29+
processResult,
30+
updateState,
31+
} = options || {};
2432
const state: RestorableOperationState<TState> = resumeFrom
2533
? deserializeState(resumeFrom)
2634
: ({} as RestorableOperationState<TState>);
27-
2835
const operation = new GenericPollOperation(
2936
state,
3037
lro,
31-
options?.lroResourceLocationConfig,
32-
options?.processResult,
33-
options?.updateState,
34-
options?.isDone
38+
!resolveOnUnsuccessful,
39+
lroResourceLocationConfig,
40+
processResult,
41+
updateState,
42+
isDone
3543
);
3644
super(operation);
45+
this.resolveOnUnsuccessful = resolveOnUnsuccessful;
3746

3847
this.config = { intervalInMs: intervalInMs };
3948
operation.setPollerConfig(this.config);

sdk/core/core-lro/src/legacy/lroEngine/models.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ export interface LroEngineOptions<TResult, TState> {
3131
* A predicate to determine whether the LRO finished processing.
3232
*/
3333
isDone?: (lastResponse: unknown, state: TState) => boolean;
34+
/**
35+
* Control whether to throw an exception if the operation failed or was canceled.
36+
*/
37+
resolveOnUnsuccessful?: boolean;
3438
}
3539

3640
export interface PollerConfig {

sdk/core/core-lro/src/legacy/lroEngine/operation.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export class GenericPollOperation<TResult, TState extends PollOperationState<TRe
3939
constructor(
4040
public state: RestorableOperationState<TState>,
4141
private lro: LongRunningOperation,
42+
private setErrorAsResult: boolean,
4243
private lroResourceLocationConfig?: LroResourceLocationConfig,
4344
private processResult?: (result: unknown, state: TState) => TResult,
4445
private updateState?: (state: TState, lastResponse: RawResponse) => void,
@@ -62,6 +63,7 @@ export class GenericPollOperation<TResult, TState extends PollOperationState<TRe
6263
stateProxy,
6364
resourceLocationConfig: this.lroResourceLocationConfig,
6465
processResult: this.processResult,
66+
setErrorAsResult: this.setErrorAsResult,
6567
})),
6668
};
6769
}
@@ -84,6 +86,7 @@ export class GenericPollOperation<TResult, TState extends PollOperationState<TRe
8486
setDelay: (intervalInMs) => {
8587
this.pollerConfig!.intervalInMs = intervalInMs;
8688
},
89+
setErrorAsResult: this.setErrorAsResult,
8790
});
8891
}
8992
options?.fireProgress?.(this.state);

sdk/core/core-lro/src/legacy/poller.ts

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ export class PollerCancelledError extends Error {
102102
export abstract class Poller<TState extends PollOperationState<TResult>, TResult>
103103
implements PollerLike<TState, TResult>
104104
{
105+
/** controls whether to throw an error if the operation failed or was canceled. */
106+
protected resolveOnUnsuccessful: boolean = false;
105107
private stopped: boolean = true;
106108
private resolve?: (value: TResult) => void;
107109
private reject?: (error: PollerStoppedError | PollerCancelledError | Error) => void;
@@ -247,14 +249,10 @@ export abstract class Poller<TState extends PollOperationState<TResult>, TResult
247249
*/
248250
private async pollOnce(options: { abortSignal?: AbortSignalLike } = {}): Promise<void> {
249251
if (!this.isDone()) {
250-
try {
251-
this.operation = await this.operation.update({
252-
abortSignal: options.abortSignal,
253-
fireProgress: this.fireProgress.bind(this),
254-
});
255-
} catch (e: any) {
256-
this.operation.state.error = e;
257-
}
252+
this.operation = await this.operation.update({
253+
abortSignal: options.abortSignal,
254+
fireProgress: this.fireProgress.bind(this),
255+
});
258256
}
259257
this.processUpdatedState();
260258
}
@@ -302,21 +300,26 @@ export abstract class Poller<TState extends PollOperationState<TResult>, TResult
302300
private processUpdatedState(): void {
303301
if (this.operation.state.error) {
304302
this.stopped = true;
305-
this.reject!(this.operation.state.error);
306-
throw this.operation.state.error;
303+
if (!this.resolveOnUnsuccessful) {
304+
this.reject!(this.operation.state.error);
305+
throw this.operation.state.error;
306+
}
307307
}
308308
if (this.operation.state.isCancelled) {
309309
this.stopped = true;
310-
const error = new PollerCancelledError("Operation was canceled");
311-
this.reject!(error);
312-
throw error;
313-
} else if (this.isDone() && this.resolve) {
310+
if (!this.resolveOnUnsuccessful) {
311+
const error = new PollerCancelledError("Operation was canceled");
312+
this.reject!(error);
313+
throw error;
314+
}
315+
}
316+
if (this.isDone() && this.resolve) {
314317
// If the poller has finished polling, this means we now have a result.
315318
// However, it can be the case that TResult is instantiated to void, so
316319
// we are not expecting a result anyway. To assert that we might not
317320
// have a result eventually after finishing polling, we cast the result
318321
// to TResult.
319-
this.resolve(this.operation.state.result as TResult);
322+
this.resolve(this.getResult() as TResult);
320323
}
321324
}
322325

0 commit comments

Comments
 (0)