UNPKG

19.7 kBJavaScriptView Raw
1var __extends = (this && this.__extends) || (function () {
2 var extendStatics = function (d, b) {
3 extendStatics = Object.setPrototypeOf ||
4 ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
5 function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
6 return extendStatics(d, b);
7 };
8 return function (d, b) {
9 if (typeof b !== "function" && b !== null)
10 throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
11 extendStatics(d, b);
12 function __() { this.constructor = d; }
13 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
14 };
15})();
16var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
17 function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
18 return new (P || (P = Promise))(function (resolve, reject) {
19 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
20 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
21 function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
22 step((generator = generator.apply(thisArg, _arguments || [])).next());
23 });
24};
25var __generator = (this && this.__generator) || function (thisArg, body) {
26 var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
27 return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
28 function verb(n) { return function (v) { return step([n, v]); }; }
29 function step(op) {
30 if (f) throw new TypeError("Generator is already executing.");
31 while (g && (g = 0, op[0] && (_ = 0)), _) try {
32 if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
33 if (y = 0, t) op = [op[0] & 2, t.value];
34 switch (op[0]) {
35 case 0: case 1: t = op; break;
36 case 4: _.label++; return { value: op[1], done: false };
37 case 5: _.label++; y = op[1]; op = [0]; continue;
38 case 7: op = _.ops.pop(); _.trys.pop(); continue;
39 default:
40 if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
41 if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
42 if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
43 if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
44 if (t[2]) _.ops.pop();
45 _.trys.pop(); continue;
46 }
47 op = body.call(thisArg, _);
48 } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
49 if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
50 }
51};
52var __values = (this && this.__values) || function(o) {
53 var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
54 if (m) return m.call(o);
55 if (o && typeof o.length === "number") return {
56 next: function () {
57 if (o && i >= o.length) o = void 0;
58 return { value: o && o[i++], done: !o };
59 }
60 };
61 throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
62};
63// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
64// SPDX-License-Identifier: Apache-2.0
65/**
66 * @private For internal Amplify use.
67 *
68 * Creates a new scope for promises, observables, and other types of work or
69 * processes that may be running in the background. This manager provides
70 * an singular entrypoint to request termination and await completion.
71 *
72 * As work completes on its own prior to close, the manager removes them
73 * from the registry to avoid holding references to completed jobs.
74 */
75var BackgroundProcessManager = /** @class */ (function () {
76 /**
77 * Creates a new manager for promises, observables, and other types
78 * of work that may be running in the background. This manager provides
79 * a centralized mechanism to request termination and await completion.
80 */
81 function BackgroundProcessManager() {
82 /**
83 * A string indicating whether the manager is accepting new work ("Open"),
84 * waiting for work to complete ("Closing"), or fully done with all
85 * submitted work and *not* accepting new jobs ("Closed").
86 */
87 this._state = BackgroundProcessManagerState.Open;
88 /**
89 * The list of outstanding jobs we'll need to wait for upon `close()`
90 */
91 this.jobs = new Set();
92 }
93 BackgroundProcessManager.prototype.add = function (jobOrDescription, optionalDescription) {
94 var job;
95 var description;
96 if (typeof jobOrDescription === 'string') {
97 job = undefined;
98 description = jobOrDescription;
99 }
100 else {
101 job = jobOrDescription;
102 description = optionalDescription;
103 }
104 var error = this.closedFailure(description);
105 if (error)
106 return error;
107 if (job === undefined) {
108 return this.addHook(description);
109 }
110 else if (typeof job === 'function') {
111 return this.addFunction(job, description);
112 }
113 else if (job instanceof BackgroundProcessManager) {
114 return this.addManager(job, description);
115 }
116 else {
117 throw new Error('If `job` is provided, it must be an Observable, Function, or BackgroundProcessManager.');
118 }
119 };
120 /**
121 * Adds a **cleaner** function that doesn't immediately get executed.
122 * Instead, the caller gets a **terminate** function back. The *cleaner* is
123 * invoked only once the mananger *closes* or the returned **terminate**
124 * function is called.
125 *
126 * @param clean The cleanup function.
127 * @param description Optional description to help identify pending jobs.
128 * @returns A terminate function.
129 */
130 BackgroundProcessManager.prototype.addCleaner = function (clean, description) {
131 var _this = this;
132 var _a = this.addHook(description), resolve = _a.resolve, onTerminate = _a.onTerminate;
133 var proxy = function () { return __awaiter(_this, void 0, void 0, function () {
134 return __generator(this, function (_a) {
135 switch (_a.label) {
136 case 0: return [4 /*yield*/, clean()];
137 case 1:
138 _a.sent();
139 resolve();
140 return [2 /*return*/];
141 }
142 });
143 }); };
144 onTerminate.then(proxy);
145 return proxy;
146 };
147 BackgroundProcessManager.prototype.addFunction = function (job, description) {
148 // the function we call when we want to try to terminate this job.
149 var terminate;
150 // the promise the job can opt into listening to for termination.
151 var onTerminate = new Promise(function (resolve) {
152 terminate = resolve;
153 });
154 // finally! start the job.
155 var jobResult = job(onTerminate);
156 // depending on what the job gives back, register the result
157 // so we can monitor for completion.
158 if (typeof (jobResult === null || jobResult === void 0 ? void 0 : jobResult.then) === 'function') {
159 this.registerPromise(jobResult, terminate, description);
160 }
161 // At the end of the day, or you know, method call, it doesn't matter
162 // what the return value is at all; we just pass it through to the
163 // caller.
164 return jobResult;
165 };
166 BackgroundProcessManager.prototype.addManager = function (manager, description) {
167 var _this = this;
168 this.addCleaner(function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) {
169 switch (_a.label) {
170 case 0: return [4 /*yield*/, manager.close()];
171 case 1: return [2 /*return*/, _a.sent()];
172 }
173 }); }); }, description);
174 };
175 /**
176 * Creates and registers a fabricated job for processes that need to operate
177 * with callbacks/hooks. The returned `resolve` and `reject`
178 * functions can be used to signal the job is done successfully or not.
179 * The returned `onTerminate` is a promise that will resolve when the
180 * manager is requesting the termination of the job.
181 *
182 * @param description Optional description to help identify pending jobs.
183 * @returns `{ resolve, reject, onTerminate }`
184 */
185 BackgroundProcessManager.prototype.addHook = function (description) {
186 // the resolve/reject functions we'll provide to the caller to signal
187 // the state of the job.
188 var resolve;
189 var reject;
190 // the underlying promise we'll use to manage it, pretty much like
191 // any other promise.
192 var promise = new Promise(function (res, rej) {
193 resolve = res;
194 reject = rej;
195 });
196 // the function we call when we want to try to terminate this job.
197 var terminate;
198 // the promise the job can opt into listening to for termination.
199 var onTerminate = new Promise(function (resolveTerminate) {
200 terminate = resolveTerminate;
201 });
202 this.registerPromise(promise, terminate, description);
203 return {
204 resolve: resolve,
205 reject: reject,
206 onTerminate: onTerminate,
207 };
208 };
209 /**
210 * Adds a Promise based job to the list of jobs for monitoring and listens
211 * for either a success or failure, upon which the job is considered "done"
212 * and removed from the registry.
213 *
214 * @param promise A promise that is on its way to being returned to a
215 * caller, which needs to be tracked as a background job.
216 * @param terminate The termination function to register, which can be
217 * invoked to request the job stop.
218 * @param description Optional description to help identify pending jobs.
219 */
220 BackgroundProcessManager.prototype.registerPromise = function (promise, terminate, description) {
221 var _this = this;
222 var jobEntry = { promise: promise, terminate: terminate, description: description };
223 this.jobs.add(jobEntry);
224 // in all of my testing, it is safe to multi-subscribe to a promise.
225 // so, rather than create another layer of promising, we're just going
226 // to hook into the promise we already have, and when it's done
227 // (successfully or not), we no longer need to wait for it upon close.
228 //
229 // sorry this is a bit hand-wavy:
230 //
231 // i believe we use `.then` and `.catch` instead of `.finally` because
232 // `.finally` is invoked in a different order in the sequence, and this
233 // breaks assumptions throughout and causes failures.
234 promise
235 .then(function () {
236 _this.jobs.delete(jobEntry);
237 })
238 .catch(function () {
239 _this.jobs.delete(jobEntry);
240 });
241 };
242 Object.defineProperty(BackgroundProcessManager.prototype, "length", {
243 /**
244 * The number of jobs being waited on.
245 *
246 * We don't use this for anything. It's just informational for the caller,
247 * and can be used in logging and testing.
248 *
249 * @returns the number of jobs.
250 */
251 get: function () {
252 return this.jobs.size;
253 },
254 enumerable: false,
255 configurable: true
256 });
257 Object.defineProperty(BackgroundProcessManager.prototype, "state", {
258 /**
259 * The execution state of the manager. One of:
260 *
261 * 1. "Open" -> Accepting new jobs
262 * 1. "Closing" -> Not accepting new work. Waiting for jobs to complete.
263 * 1. "Closed" -> Not accepting new work. All submitted jobs are complete.
264 */
265 get: function () {
266 return this._state;
267 },
268 enumerable: false,
269 configurable: true
270 });
271 Object.defineProperty(BackgroundProcessManager.prototype, "pending", {
272 /**
273 * The registered `description` of all still-pending jobs.
274 *
275 * @returns descriptions as an array.
276 */
277 get: function () {
278 return Array.from(this.jobs).map(function (job) { return job.description; });
279 },
280 enumerable: false,
281 configurable: true
282 });
283 Object.defineProperty(BackgroundProcessManager.prototype, "isOpen", {
284 /**
285 * Whether the manager is accepting new jobs.
286 */
287 get: function () {
288 return this._state === BackgroundProcessManagerState.Open;
289 },
290 enumerable: false,
291 configurable: true
292 });
293 Object.defineProperty(BackgroundProcessManager.prototype, "isClosing", {
294 /**
295 * Whether the manager is rejecting new work, but still waiting for
296 * submitted work to complete.
297 */
298 get: function () {
299 return this._state === BackgroundProcessManagerState.Closing;
300 },
301 enumerable: false,
302 configurable: true
303 });
304 Object.defineProperty(BackgroundProcessManager.prototype, "isClosed", {
305 /**
306 * Whether the manager is rejecting work and done waiting for submitted
307 * work to complete.
308 */
309 get: function () {
310 return this._state === BackgroundProcessManagerState.Closed;
311 },
312 enumerable: false,
313 configurable: true
314 });
315 BackgroundProcessManager.prototype.closedFailure = function (description) {
316 if (!this.isOpen) {
317 return Promise.reject(new BackgroundManagerNotOpenError([
318 "The manager is ".concat(this.state, "."),
319 "You tried to add \"".concat(description, "\"."),
320 "Pending jobs: [\n".concat(this.pending
321 .map(function (t) { return ' ' + t; })
322 .join(',\n'), "\n]"),
323 ].join('\n')));
324 }
325 };
326 /**
327 * Signals jobs to stop (for those that accept interruptions) and waits
328 * for confirmation that jobs have stopped.
329 *
330 * This immediately puts the manager into a closing state and just begins
331 * to reject new work. After all work in the manager is complete, the
332 * manager goes into a `Completed` state and `close()` returns.
333 *
334 * This call is idempotent.
335 *
336 * If the manager is already closing or closed, `finalCleaup` is not executed.
337 *
338 * @param onClosed
339 * @returns The settled results of each still-running job's promise. If the
340 * manager is already closed, this will contain the results as of when the
341 * manager's `close()` was called in an `Open` state.
342 */
343 BackgroundProcessManager.prototype.close = function () {
344 return __awaiter(this, void 0, void 0, function () {
345 var _a, _b, job;
346 var e_1, _c;
347 return __generator(this, function (_d) {
348 switch (_d.label) {
349 case 0:
350 if (!this.isOpen) return [3 /*break*/, 2];
351 this._state = BackgroundProcessManagerState.Closing;
352 try {
353 for (_a = __values(Array.from(this.jobs)), _b = _a.next(); !_b.done; _b = _a.next()) {
354 job = _b.value;
355 try {
356 job.terminate();
357 }
358 catch (error) {
359 // Due to potential races with a job's natural completion, it's
360 // reasonable to expect the termination call to fail. Hence,
361 // not logging as an error.
362 console.warn("Failed to send termination signal to job. Error: ".concat(error.message), job);
363 }
364 }
365 }
366 catch (e_1_1) { e_1 = { error: e_1_1 }; }
367 finally {
368 try {
369 if (_b && !_b.done && (_c = _a.return)) _c.call(_a);
370 }
371 finally { if (e_1) throw e_1.error; }
372 }
373 // Use `allSettled()` because we want to wait for all to finish. We do
374 // not want to stop waiting if there is a failure.
375 this._closingPromise = Promise.allSettled(Array.from(this.jobs).map(function (j) { return j.promise; }));
376 return [4 /*yield*/, this._closingPromise];
377 case 1:
378 _d.sent();
379 this._state = BackgroundProcessManagerState.Closed;
380 _d.label = 2;
381 case 2: return [2 /*return*/, this._closingPromise];
382 }
383 });
384 });
385 };
386 /**
387 * Signals the manager to start accepting work (again) and returns once
388 * the manager is ready to do so.
389 *
390 * If the state is already `Open`, this call is a no-op.
391 *
392 * If the state is `Closed`, this call simply updates state and returns.
393 *
394 * If the state is `Closing`, this call waits for completion before it
395 * updates the state and returns.
396 */
397 BackgroundProcessManager.prototype.open = function () {
398 return __awaiter(this, void 0, void 0, function () {
399 return __generator(this, function (_a) {
400 switch (_a.label) {
401 case 0:
402 if (!this.isClosing) return [3 /*break*/, 2];
403 return [4 /*yield*/, this.close()];
404 case 1:
405 _a.sent();
406 _a.label = 2;
407 case 2:
408 this._state = BackgroundProcessManagerState.Open;
409 return [2 /*return*/];
410 }
411 });
412 });
413 };
414 return BackgroundProcessManager;
415}());
416export { BackgroundProcessManager };
417/**
418 *
419 */
420var BackgroundManagerNotOpenError = /** @class */ (function (_super) {
421 __extends(BackgroundManagerNotOpenError, _super);
422 function BackgroundManagerNotOpenError(message) {
423 return _super.call(this, "BackgroundManagerNotOpenError: ".concat(message)) || this;
424 }
425 return BackgroundManagerNotOpenError;
426}(Error));
427export { BackgroundManagerNotOpenError };
428/**
429 * All possible states a `BackgroundProcessManager` instance can be in.
430 */
431export var BackgroundProcessManagerState;
432(function (BackgroundProcessManagerState) {
433 /**
434 * Accepting new jobs.
435 */
436 BackgroundProcessManagerState["Open"] = "Open";
437 /**
438 * Not accepting new jobs. Waiting for submitted jobs to complete.
439 */
440 BackgroundProcessManagerState["Closing"] = "Closing";
441 /**
442 * Not accepting new jobs. All submitted jobs are complete.
443 */
444 BackgroundProcessManagerState["Closed"] = "Closed";
445})(BackgroundProcessManagerState || (BackgroundProcessManagerState = {}));