Canceling a promise in a Vuex action

I am looking to implement the ability to cancel a running promise in my Vue component, specifically a promise returned by a Vuex action.

In my scenario, the Vuex action is continuously polling an endpoint for status updates, and I need the capability to stop this polling when a certain user action (such as closing a function) occurs.

Although I have created a custom CancellablePromise class inspired by a solution from Stack Overflow, it does not seem to work seamlessly with Vuex.

Cancellable promise class (from )

export class CancellablePromise<T> extends Promise<T> {
  private onCancel: () => void;

  constructor(
    executor: (
      resolve: (value?: T | PromiseLike<T>) => void,
      reject: (reason?: any) => void,
      onCancel: (cancelHandler: () => void) => void
    ) => void
  ) {
    let onCancel: () => void;
    super((rs, rj) =>
      executor(rs, rj, (ch: () => void) => {
        onCancel = ch;
      })
    );
    this.onCancel = onCancel;
  }

  public cancel(): void {
    if (this.onCancel) {
      this.onCancel();
    }
  }
}

Action

async [SomeAction.foo]({ state, dispatch, commit, rootGetters }) {
    const cancellablePromise = new CancellablePromise<any>((resolve, reject, onCancel) => {
      const interval = setInterval(async () => {
        const status = await dispatch(SomeAction.bar);
        if (status === "goodstatus") {
          clearInterval(interval);
          resolve();
        } else if (status === "badstatus") {
          clearInterval(interval);
          reject();
        }
      }, 2000);

      onCancel(() => {
        clearInterval(interval);
        reject();
      });
    });

    return cancellablePromise;
  }

Component

data: (() => {
  promise: undefined as CancellablePromise<any> | undefined
}),

async call() {
  this.promise = this.$store
    .dispatch(SomeAction.foo)
    .then(response => {
      // do something
    }) as CancellablePromise<any>;
},

close(): void {
  if (this.promise) {
    this.promise.cancel(); // outputs cancel is not a function
  }
}

An issue arises in the close function where this.promise.cancel throws an error stating that cancel is not a function.

This appears to be because the object returned by dispatch is indeed a Promise rather than a CancellablePromise. My suspicion stems from examining the Vuex source code, which suggests that a new Promise is created from the Promise returned by the action. While I may not be well-versed in TypeScript's type system, it seems like my CancellablePromise is somehow not being utilized correctly here.

How can I achieve my intended functionality in this situation?

Answer №1

Extending a Promise can be messy and unnecessary. It's more common

  • to expose a Promise's reject method to the broader audience (outside the Promise's constructor) and call it whenever needed to trigger the Promise's error path.
  • to race a "cancellation Promise" against the targeted Promise, but in this case, as the promisification of the setInterval process provides a reject method, it is not essential.

Something similar to this could work (not tested).

Action

async [SomeAction.foo]({ state, dispatch, commit, rootGetters }) {
    let reject_, interval;
    const promise = new Promise((resolve, reject) => {
        reject_ = reject; // externalize the reject method
        interval = setInterval(async () => {
            const status = await dispatch(SomeAction.bar);
            if (status === 'goodstatus') {
                resolve();
            } else if (status === 'badstatus') {
                reject(new Error(status)); // for instance
            } else {
                // ignore other states ???
            }
        }, 2000);
    });
    promise.cancel = reject_; // enhance promise with its own reject method.
    return promise.always(() => { clearInterval(interval) }); // clear the interval regardless of how the promise resolves (resolve() or reject() above, or promise.cancel() externally).
}

Component

data: (() => {
    cancel: null
}),
async call() {
    this.close(new Error('new call was made before previous call completed')); // might be a good idea
    let promise = this.$store.dispatch(SomeAction.foo); // do not chain .then() yet, as you will lose the .cancel property.
    this.cancel = promise.cancel; // save the ability to force-reject the promise;
    return promise.then(response => { // now you can chain .then()
        // take action
    })
    .catch(error => {
        console.log(error);
        throw error;
    });
},
close(reason): void {
    if (this.cancel) {
        this.cancel(reason || new Error('cancelled'));
    }
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

How to eliminate all <style> tags across the board using Regex in JavaScript

Is there a way to utilize regex in JavaScript for removing all instances of <style>, <style type="text/css">, </style>, and <style type="text/css"/>? I want the outcome to only present the CSS without any style tags. The code below ...

JavaScript/DOM - What sets apart a "CSS Selector" from an attribute?

When it comes to excluding declarative event handlers: <a href='#' onclick=<handler> ... /> Is there a significant difference between an Attribute and a CSS Selector? For example, if I define my own attribute: <a href='#&a ...

Update the display using a button without the need to refresh the entire webpage

I am currently working on a website project that requires randomized output. I have successfully implemented a solution using Javascript, but the output only changes when the page is reloaded. Is there a way to update the output without refreshing the en ...

Struggling to generate a div using my JS/jQuery code

Currently working on a post-it web application to improve my skills in JavaScript and jQuery. I've encountered a few errors that have me stumped. Although I'm new to StackOverflow, I often use it as a helpful resource. You can find the code here ...

Transforming a NestJS/VueJS application into a Docker container

We are in the process of containerizing a NestJS REST API and a VueJS application using Docker. To achieve this, we have created a docker-compose.yaml file and two Dockerfiles: docker-compose.yml: version: '3.8' services: fox-deck-app: b ...

Ran into a situation where Nextjs13 had two children sharing the same key

Currently, I am in the process of creating a booking form using shadcn/ui within nextjs13. As part of this, I am mapping over different hairstyles listed in my postgres database to generate selectable options for users. However, during this process, I enco ...

What is the process by which Angular 2 handles imports?

Currently diving into the world of Angular2 with TypeScript. I understand that SystemJS is crucial for enabling the import feature, like this example: import { bootstrap } from "angular2/platform/browser"; While this all makes sense, I find myself questi ...

Is it the browser's responsibility to convert ES6 to ES5 internally?

Given the support for ES6 in modern browsers, do they internally convert ES6 to ES5 before executing the code? Or can they process ES6 natively using a C++ engine? If they are able to run ES6 directly, how do they guarantee that executing ES6 code produce ...

Tips for transmitting custom information through FullCalender Vue utilizing eventDataTransform

I have unique data points for each event (such as event.organizer) that I would like to include in the calendar within the event section. After reviewing the documentation (https://fullcalendar.io/docs/eventDataTransform), I found it lacking, especially w ...

UI not updating correctly due to computed property across multiple tabs

I rely on vuex for managing state and authenticating users using firebase To maintain state persistence, I utilize vuex-persisted state to store data in cookies Within my vuex store, I handle user data (such as username and login status) as demonstrated b ...

Having difficulty in animating the upward movement of textbox2

I've got a form that contains 2 buttons and 2 textareas. When the form loads, I want to display only section1 (button, textarea) and the section2 button. Upon clicking the section2 button, my aim is to hide the section1 textarea, reveal the section2 t ...

Transform the structure of an object using Ramda from its original form

Is it possible to transform an object by modifying and filtering it to create a different shape that is easier to work with after making an API request? I've been struggling to find elegant solutions that don't involve using path and prop for eve ...

Persisting dynamically generated table information into a multidimensional array

I've created a dynamic table and now I'm trying to extract the data from it and store it in a multidimensional array. However, I keep encountering an exception/error that says "Cannot set property of 0 to undefined". https://i.sstatic.net/W8B9j.p ...

Is it possible to modify the sub/child divs within a draggable parent div and assign them a different class?

Greetings, after being a long-time reader, I have finally decided to post for the first time. In the process of creating a webpage with jQuery drag and drop divs, I am curious about how to change the class of a child div within a draggable div once it is d ...

Is it possible to create a transparent colored foreground in HTML?

Looking to create a translucent foreground color on a webpage? I'm aiming to give a hint of red to the overall look of the website. Edit: Just to clarify, I am not referring to changing font colors. I want a color that can overlay the entire page wit ...

Getting rid of the empty spaces between the lines of cards in Bootstrap 4

I want to eliminate the vertical space between the cards in my layout. Essentially, I want the cards to maximize the available space. I am open to using a plugin if necessary, but I have not been able to find any relevant information online (maybe I used t ...

Error occurs when attempting to read the 'map' properties of null because the JSON array is double nested

Within my code, I am attempting to access the URLs of two thumbnails in the JSON data below. Currently, I can only retrieve the information from the first array: <>{post.attributes.description}</> However, I am encountering difficulty retrievi ...

Guide on creating a square within an element using JavaScript

After conducting thorough research, I find myself unsure of the best course of action. My situation involves a Kendo Grid (table) with 3 rows and 3 columns. Initially, the table displays only the first column, populated upon the page's initial load. S ...

The Angular performance may be impacted by the constant recalculation of ngStyle when clicking on various input fields

I am facing a frustrating performance issue. Within my component, I have implemented ngStyle and I would rather not rewrite it. However, every time I interact with random input fields on the same page (even from another component), the ngStyle recalculate ...

Error: JSON parsing failed due to an unexpected token 's' at position 1

I am currently developing a plugin and facing an issue with updating post meta in Wordpress. I have created a PHP code to handle this task by receiving an array/object/json array: public static function save_settings_fields(){ $myArray = json_decode( ...