Ver Fonte

[frontend] Improve logic in cancellablePromise and add tests

Johan Ahlen há 5 anos atrás
pai
commit
dca097e71d

+ 92 - 0
desktop/core/src/desktop/js/api/cancellablePromise.test.ts

@@ -0,0 +1,92 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { noop } from 'lodash';
+
+import { CancellablePromise } from './cancellablePromise';
+
+describe('cancellablePromise.ts', () => {
+  it('it should not reject a running promise when cancelled', async () => {
+    let resolved = false;
+    let rejected = false;
+    let resolveHandler: () => void = noop;
+    const subject = new CancellablePromise<void>(resolve => {
+      resolveHandler = resolve;
+    });
+    subject.cancel();
+    if (resolveHandler) {
+      resolveHandler();
+    }
+    try {
+      await subject;
+      resolved = true;
+    } catch {
+      rejected = true;
+    }
+    expect(resolved).toBeTruthy();
+    expect(rejected).toBeFalsy();
+  });
+
+  it('it should set cancelled on cancel of a running promise', () => {
+    const subject = new CancellablePromise<void>(() => {
+      // Do nothing
+    });
+    subject.catch(noop);
+    expect(subject.cancelled).toBeFalsy();
+    subject.cancel();
+    expect(subject.cancelled).toBeTruthy();
+  });
+
+  it('should not cancel a resolved promises', () => {
+    const subject = CancellablePromise.resolve();
+    subject.catch(noop);
+    subject.cancel();
+    expect(subject.cancelled).toBeFalsy();
+  });
+
+  it('should not cancel a rejected promises', () => {
+    const subject = CancellablePromise.reject();
+    subject.catch(noop);
+    subject.cancel();
+    expect(subject.cancelled).toBeFalsy();
+  });
+
+  it('should not call onCancel on a resolved promises', () => {
+    let onCancelCalled = false;
+    const subject = new CancellablePromise<void>((resolve, reject, onCancel) => {
+      onCancel(() => {
+        onCancelCalled = true;
+      });
+      resolve();
+    });
+    subject.catch(noop);
+    subject.cancel();
+    expect(onCancelCalled).toBeFalsy();
+  });
+
+  it('should not call onCancel on a rejected promises', () => {
+    let onCancelCalled = false;
+    const subject = new CancellablePromise<void>((resolve, reject, onCancel) => {
+      onCancel(() => {
+        onCancelCalled = true;
+      });
+      reject('banana');
+    });
+    subject.catch(noop);
+    subject.cancel();
+    expect(onCancelCalled).toBeFalsy();
+  });
+});

+ 21 - 19
desktop/core/src/desktop/js/api/cancellablePromise.ts

@@ -20,8 +20,8 @@ export interface Cancellable {
 
 export class CancellablePromise<T> extends Promise<T> implements Cancellable {
   private readonly onCancel?: () => void;
-  private readonly rejectHandler?: (reason?: unknown) => void;
   private cancelPrevented?: boolean;
+  private completed: () => boolean;
   cancelled?: boolean;
 
   constructor(
@@ -32,28 +32,30 @@ export class CancellablePromise<T> extends Promise<T> implements Cancellable {
     ) => void
   ) {
     let onCancel: undefined | (() => void) = undefined;
-    let rejectHandler: undefined | ((reason?: unknown) => void) = undefined;
-    super((resolve, reject) => {
-      rejectHandler = reject;
-      return handlers(resolve, reject, (cancelHandler: () => void) => (onCancel = cancelHandler));
-    });
-    this.rejectHandler = rejectHandler;
+    let completed = false;
+    super((resolve, reject) =>
+      handlers(
+        val => {
+          completed = true;
+          resolve(val);
+        },
+        err => {
+          completed = true;
+          reject(err);
+        },
+        (cancelHandler: () => void) => (onCancel = cancelHandler)
+      )
+    );
     this.onCancel = onCancel;
+    this.completed = () => completed;
   }
 
-  async cancel(): Promise<void> {
-    if (!this.cancelPrevented) {
-      const testSymbol = Symbol();
-      const firstToFinish = await Promise.race([this, Promise.resolve(testSymbol)]);
-      if (firstToFinish === testSymbol) {
-        if (this.onCancel) {
-          this.onCancel();
-        }
-        if (this.rejectHandler) {
-          this.rejectHandler();
-        }
-        this.cancelled = true;
+  cancel(): void {
+    if (!this.completed() && !this.cancelled && !this.cancelPrevented) {
+      if (this.onCancel) {
+        this.onCancel();
       }
+      this.cancelled = true;
     }
   }