**Updated**, I now use a much simplified version of `CancellablePromise`, I'll plan to publish it as an independent NPM package soon:

    /**
     * Class representing a cancellable promise.
     * @extends Promise
     */
    class CancellablePromise extends Promise {
      static get [Symbol.species]() {
        return Promise;
      }
    
      /**
       * Create an instance of CancellablePromise promise.
       * @param {Function} executor - accepts an object with callbacks 
       *  and a token: { resolve, reject, cancel, token }
       * @param {CancellationToken} token - a cancellation token.
       */
      constructor(executor, token) {
        const withCancellation = async () => {
          const linkedSource = new CancellationTokenSource(token);
    
          try {
            const linkedToken = linkedSource.token;
            linkedToken.throwIfCancellationRequested();
    
            const deferred = new Deferred();
            linkedToken.register(() => deferred.cancel());
    
            executor({ 
              resolve: value => deferred.resolve(value),
              reject: error => deferred.reject(error),
              cancel: () => linkedSource.cancel(),
              token: linkedToken,
            });
     
            return await deferred;
          }
          finally {
            // this will free the linkedToken registration
            linkedSource.close();
          }
        };
    
        super((resolve, reject) => withCancellation().then(resolve, reject));
      }
    }