Chromium Code Reviews| Index: web/inc/rpc/client.ts |
| diff --git a/web/inc/rpc/client.ts b/web/inc/rpc/client.ts |
| index 9d3928ff54b2de3008a4d84a11a3adcb5b94f042..3829aa87a07b730d592e706e02bacbda6cd88c18 100644 |
| --- a/web/inc/rpc/client.ts |
| +++ b/web/inc/rpc/client.ts |
| @@ -73,33 +73,28 @@ namespace luci { |
| */ |
| call<T, R>(service: string, method: string, request?: T): Promise<R> { |
| let transientRetry = new RetryIterator(this.transientRetry); |
| - let doCall = (): Promise<R> => { |
| - // Configure the client for this request. |
| - this.pc.service = service; |
| - this.pc.method = method; |
| - this.pc.request = request; |
| - |
| - // Execute the configured request. |
| - let callPromise: Promise<R> = this.pc.call().completes; |
| - return callPromise.then((resp: any) => resp.response) |
| - .catch((err: Error) => { |
| - // Is this a transient error? |
| - if (isTransientError(err)) { |
| - let delay = transientRetry.next(); |
| - if (delay >= 0) { |
| - console.warn( |
| - `Transient error calling ` + |
| - `${service}.${method} with params:`, |
| - request, `:`, err, `; retrying after ${delay}ms.`); |
| - return luci.sleepPromise(delay).then(doCall); |
| - } |
| - } |
| + return transientRetry |
| + .do( |
| + () => { |
| + // Configure the client for this request. |
| + this.pc.service = service; |
| + this.pc.method = method; |
| + this.pc.request = request; |
| - // Non-transient, throw the error. |
| - throw err; |
| - }); |
| - }; |
| - return doCall(); |
| + // Execute the configured request. |
| + return this.pc.call().completes; |
| + }, |
| + (err: Error, delay: number) => { |
| + // Is this a transient error? |
| + if (!isTransientError(err)) { |
| + throw err; |
| + } |
| + console.warn( |
| + `Transient error calling ` + |
| + `${service}.${method} with params:`, |
| + request, `:`, err, `; retrying after ${delay}ms.`); |
| + }) |
| + .then((resp: any) => resp.response); |
| } |
| } |
| @@ -216,20 +211,29 @@ namespace luci { |
| export type Retry = { |
| // The number of retries to perform before failing. If undefined, will retry |
| // indefinitely. |
| - retries: number | undefined; |
| + retries?: number; |
| // The amount of time to delay in between retry attempts, in milliseconds. |
| // If undefined or < 0, no delay will be imposed. |
| delay: number; |
| // The maximum delay to apply, in milliseconds. If > 0 and delay scales past |
| // "maxDelay", it will be capped at "maxDelay". |
| - maxDelay: number | undefined; |
| + maxDelay?: number; |
| // delayScaling is the multiplier applied to "delay" in between retries. If |
| // undefined or <= 1, DEFAULT_DELAY_SCALING will be used. |
| delayScaling?: number; |
| }; |
| + /** RetryCallback is an optional callback type used in "do". */ |
| + export type RetryCallback = (err: Error, delay: number) => void; |
| + |
| + /** |
| + * Stopped is a sentinel error thrown by RetryIterator when it runs out of |
| + * retries. |
| + */ |
| + export const STOPPED = new Error('retry stopped'); |
| + |
| /** |
| * Generic exponential backoff retry delay generator. |
| * |
| @@ -260,12 +264,12 @@ namespace luci { |
| * @returns the next delay, in milliseconds. If there are no more retries, |
| * returns undefined. |
| */ |
| - next(): number|undefined { |
| + next(): number { |
| // Apply retries, if they have been enabled. |
| if (this.retries !== undefined) { |
| if (this.retries <= 0) { |
| // No more retries remaining. |
| - return undefined; |
| + throw STOPPED; |
| } |
| this.retries--; |
| } |
| @@ -277,5 +281,53 @@ namespace luci { |
| } |
| return delay; |
| } |
| + |
| + /** |
| + * Executes a Promise, retrying if the Promise raises an error. |
| + * |
| + * "do" iteratively tries to execute a Promise, generated by "gen". If that |
| + * Promise raises an error, "do" will retry until it either runs out of |
| + * retries, or the Promise does not return an error. Each retry, "do" will |
| + * invoke "gen" again to generate a new Promise. |
| + * |
| + * An optional "onError" callback can be supplied. If it is, it will be |
| + * invoked in between each retry. The callback may, itself, throw, in which |
| + * case the retry loop will stop. This can be used for reporting and/or |
| + * selective retries. |
| + * |
| + * @param gen Promise generator function for retries. |
| + * @param onError optional callback to be invoked in between retries. |
| + * |
| + * @throws any the error generated by "gen"'s Promise, if out of retries, |
|
nodir
2017/03/08 07:41:28
any error
dnj
2017/03/08 08:50:15
"any" is the type
nodir
2017/03/13 19:48:21
Acknowledged.
|
| + * or the error raised by onError if it chooses to throw. |
| + */ |
| + do |
|
nodir
2017/03/08 07:41:28
why line break?
this causes extra indentation in t
dnj
2017/03/08 08:50:15
clang-format put it here.
nodir
2017/03/13 19:48:21
Acknowledged.
|
| + <T>(gen: () => Promise<T>, onError?: RetryCallback): Promise<T> { |
| + let onErr = (err: Error): Promise<T> => { |
| + let delay: number; |
| + try { |
| + delay = this.next(); |
| + } catch (e) { |
| + if (e !== STOPPED) { |
| + console.warn('Unexpected error generating next delay:', e); |
| + } |
| + |
| + // If we could not generate another retry delay, raise the initial |
| + // Promise's error. |
| + throw err; |
| + } |
| + |
| + if (onError) { |
| + // Note: this may throw. |
| + onError(err, delay); |
| + } |
| + |
| + return luci.sleepPromise(delay).then(() => { |
|
nodir
2017/03/08 07:41:28
return luci.sleepPromise(delay).then(() => gen().c
dnj
2017/03/08 08:50:15
clang-format
|
| + return gen().catch(onErr); |
| + }); |
| + }; |
| + |
| + return gen().catch(onErr); |
| + } |
| } |
| } |