UNPKG

9.03 kBJavaScriptView Raw
1"use strict";
2/**
3 * -------------------------------------------------------------------------------------------
4 * Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License.
5 * See License in the project root for license information.
6 * -------------------------------------------------------------------------------------------
7 */
8Object.defineProperty(exports, "__esModule", { value: true });
9exports.RetryHandler = void 0;
10var tslib_1 = require("tslib");
11var RequestMethod_1 = require("../RequestMethod");
12var MiddlewareControl_1 = require("./MiddlewareControl");
13var MiddlewareUtil_1 = require("./MiddlewareUtil");
14var RetryHandlerOptions_1 = require("./options/RetryHandlerOptions");
15var TelemetryHandlerOptions_1 = require("./options/TelemetryHandlerOptions");
16/**
17 * @class
18 * @implements Middleware
19 * Class for RetryHandler
20 */
21var RetryHandler = /** @class */ (function () {
22 /**
23 * @public
24 * @constructor
25 * To create an instance of RetryHandler
26 * @param {RetryHandlerOptions} [options = new RetryHandlerOptions()] - The retry handler options value
27 * @returns An instance of RetryHandler
28 */
29 function RetryHandler(options) {
30 if (options === void 0) { options = new RetryHandlerOptions_1.RetryHandlerOptions(); }
31 this.options = options;
32 }
33 /**
34 *
35 * @private
36 * To check whether the response has the retry status code
37 * @param {Response} response - The response object
38 * @returns Whether the response has retry status code or not
39 */
40 RetryHandler.prototype.isRetry = function (response) {
41 return RetryHandler.RETRY_STATUS_CODES.indexOf(response.status) !== -1;
42 };
43 /**
44 * @private
45 * To check whether the payload is buffered or not
46 * @param {RequestInfo} request - The url string or the request object value
47 * @param {FetchOptions} options - The options of a request
48 * @returns Whether the payload is buffered or not
49 */
50 RetryHandler.prototype.isBuffered = function (request, options) {
51 var method = typeof request === "string" ? options.method : request.method;
52 var isPutPatchOrPost = method === RequestMethod_1.RequestMethod.PUT || method === RequestMethod_1.RequestMethod.PATCH || method === RequestMethod_1.RequestMethod.POST;
53 if (isPutPatchOrPost) {
54 var isStream = MiddlewareUtil_1.getRequestHeader(request, options, "Content-Type") === "application/octet-stream";
55 if (isStream) {
56 return false;
57 }
58 }
59 return true;
60 };
61 /**
62 * @private
63 * To get the delay for a retry
64 * @param {Response} response - The response object
65 * @param {number} retryAttempts - The current attempt count
66 * @param {number} delay - The delay value in seconds
67 * @returns A delay for a retry
68 */
69 RetryHandler.prototype.getDelay = function (response, retryAttempts, delay) {
70 var getRandomness = function () { return Number(Math.random().toFixed(3)); };
71 var retryAfter = response.headers !== undefined ? response.headers.get(RetryHandler.RETRY_AFTER_HEADER) : null;
72 var newDelay;
73 if (retryAfter !== null) {
74 if (Number.isNaN(Number(retryAfter))) {
75 newDelay = Math.round((new Date(retryAfter).getTime() - Date.now()) / 1000);
76 }
77 else {
78 newDelay = Number(retryAfter);
79 }
80 }
81 else {
82 // Adding randomness to avoid retrying at a same
83 newDelay = retryAttempts >= 2 ? this.getExponentialBackOffTime(retryAttempts) + delay + getRandomness() : delay + getRandomness();
84 }
85 return Math.min(newDelay, this.options.getMaxDelay() + getRandomness());
86 };
87 /**
88 * @private
89 * To get an exponential back off value
90 * @param {number} attempts - The current attempt count
91 * @returns An exponential back off value
92 */
93 RetryHandler.prototype.getExponentialBackOffTime = function (attempts) {
94 return Math.round((1 / 2) * (Math.pow(2, attempts) - 1));
95 };
96 /**
97 * @private
98 * @async
99 * To add delay for the execution
100 * @param {number} delaySeconds - The delay value in seconds
101 * @returns Nothing
102 */
103 RetryHandler.prototype.sleep = function (delaySeconds) {
104 return tslib_1.__awaiter(this, void 0, void 0, function () {
105 var delayMilliseconds;
106 return tslib_1.__generator(this, function (_a) {
107 delayMilliseconds = delaySeconds * 1000;
108 return [2 /*return*/, new Promise(function (resolve) { return setTimeout(resolve, delayMilliseconds); })];
109 });
110 });
111 };
112 RetryHandler.prototype.getOptions = function (context) {
113 var options;
114 if (context.middlewareControl instanceof MiddlewareControl_1.MiddlewareControl) {
115 options = context.middlewareControl.getMiddlewareOptions(this.options.constructor);
116 }
117 if (typeof options === "undefined") {
118 options = Object.assign(new RetryHandlerOptions_1.RetryHandlerOptions(), this.options);
119 }
120 return options;
121 };
122 /**
123 * @private
124 * @async
125 * To execute the middleware with retries
126 * @param {Context} context - The context object
127 * @param {number} retryAttempts - The current attempt count
128 * @param {RetryHandlerOptions} options - The retry middleware options instance
129 * @returns A Promise that resolves to nothing
130 */
131 RetryHandler.prototype.executeWithRetry = function (context, retryAttempts, options) {
132 return tslib_1.__awaiter(this, void 0, void 0, function () {
133 var delay;
134 return tslib_1.__generator(this, function (_a) {
135 switch (_a.label) {
136 case 0: return [4 /*yield*/, this.nextMiddleware.execute(context)];
137 case 1:
138 _a.sent();
139 if (!(retryAttempts < options.maxRetries && this.isRetry(context.response) && this.isBuffered(context.request, context.options) && options.shouldRetry(options.delay, retryAttempts, context.request, context.options, context.response))) return [3 /*break*/, 4];
140 ++retryAttempts;
141 MiddlewareUtil_1.setRequestHeader(context.request, context.options, RetryHandler.RETRY_ATTEMPT_HEADER, retryAttempts.toString());
142 delay = this.getDelay(context.response, retryAttempts, options.delay);
143 return [4 /*yield*/, this.sleep(delay)];
144 case 2:
145 _a.sent();
146 return [4 /*yield*/, this.executeWithRetry(context, retryAttempts, options)];
147 case 3: return [2 /*return*/, _a.sent()];
148 case 4: return [2 /*return*/];
149 }
150 });
151 });
152 };
153 /**
154 * @public
155 * @async
156 * To execute the current middleware
157 * @param {Context} context - The context object of the request
158 * @returns A Promise that resolves to nothing
159 */
160 RetryHandler.prototype.execute = function (context) {
161 return tslib_1.__awaiter(this, void 0, void 0, function () {
162 var retryAttempts, options;
163 return tslib_1.__generator(this, function (_a) {
164 switch (_a.label) {
165 case 0:
166 retryAttempts = 0;
167 options = this.getOptions(context);
168 TelemetryHandlerOptions_1.TelemetryHandlerOptions.updateFeatureUsageFlag(context, TelemetryHandlerOptions_1.FeatureUsageFlag.RETRY_HANDLER_ENABLED);
169 return [4 /*yield*/, this.executeWithRetry(context, retryAttempts, options)];
170 case 1: return [2 /*return*/, _a.sent()];
171 }
172 });
173 });
174 };
175 /**
176 * @public
177 * To set the next middleware in the chain
178 * @param {Middleware} next - The middleware instance
179 * @returns Nothing
180 */
181 RetryHandler.prototype.setNext = function (next) {
182 this.nextMiddleware = next;
183 };
184 /**
185 * @private
186 * @static
187 * A list of status codes that needs to be retried
188 */
189 RetryHandler.RETRY_STATUS_CODES = [
190 429,
191 503,
192 504, // Gateway timeout
193 ];
194 /**
195 * @private
196 * @static
197 * A member holding the name of retry attempt header
198 */
199 RetryHandler.RETRY_ATTEMPT_HEADER = "Retry-Attempt";
200 /**
201 * @private
202 * @static
203 * A member holding the name of retry after header
204 */
205 RetryHandler.RETRY_AFTER_HEADER = "Retry-After";
206 return RetryHandler;
207}());
208exports.RetryHandler = RetryHandler;
209//# sourceMappingURL=RetryHandler.js.map
\No newline at end of file