156

Is there a method for clearing the .thens of a JavaScript Promise instance?

I've written a JavaScript test framework on top of QUnit. The framework runs tests synchronously by running each one in a Promise. (Sorry for the length of this code block. I commented it as best I can, so it feels less tedious.)

/* Promise extension -- used for easily making an async step with a
       timeout without the Promise knowing anything about the function 
       it's waiting on */
$$.extend(Promise, {
    asyncTimeout: function (timeToLive, errorMessage) {
        var error = new Error(errorMessage || "Operation timed out.");
        var res, // resolve()
            rej, // reject()
            t,   // timeout instance
            rst, // reset timeout function
            p,   // the promise instance
            at;  // the returned asyncTimeout instance

        function createTimeout(reject, tempTtl) {
            return setTimeout(function () {
                // triggers a timeout event on the asyncTimeout object so that,
                // if we want, we can do stuff outside of a .catch() block
                // (may not be needed?)
                $$(at).trigger("timeout");

                reject(error);
            }, tempTtl || timeToLive);
        }

        p = new Promise(function (resolve, reject) {
            if (timeToLive != -1) {
                t = createTimeout(reject);

                // reset function -- allows a one-time timeout different
                //    from the one original specified
                rst = function (tempTtl) {
                    clearTimeout(t);
                    t = createTimeout(reject, tempTtl);
                }
            } else {
                // timeToLive = -1 -- allow this promise to run indefinitely
                // used while debugging
                t = 0;
                rst = function () { return; };
            }

            res = function () {
                clearTimeout(t);
                resolve();
            };

            rej = reject;
        });

        return at = {
            promise: p,
            resolve: res,
            reject: rej,
            reset: rst,
            timeout: t
        };
    }
});

/* framework module members... */

test: function (name, fn, options) {
    var mod = this; // local reference to framework module since promises
                    // run code under the window object

    var defaultOptions = {
        // default max running time is 5 seconds
        timeout: 5000
    }

    options = $$.extend({}, defaultOptions, options);

    // remove timeout when debugging is enabled
    options.timeout = mod.debugging ? -1 : options.timeout;

    // call to QUnit.test()
    test(name, function (assert) {
        // tell QUnit this is an async test so it doesn't run other tests
        // until done() is called
        var done = assert.async();
        return new Promise(function (resolve, reject) {
            console.log("Beginning: " + name);

            var at = Promise.asyncTimeout(options.timeout, "Test timed out.");
            $$(at).one("timeout", function () {
                // assert.fail() is just an extension I made that literally calls
                // assert.ok(false, msg);
                assert.fail("Test timed out");
            });

            // run test function
            var result = fn.call(mod, assert, at.reset);

            // if the test returns a Promise, resolve it before resolving the test promise
            if (result && result.constructor === Promise) {
                // catch unhandled errors thrown by the test so future tests will run
                result.catch(function (error) {
                    var msg = "Unhandled error occurred."
                    if (error) {
                        msg = error.message + "\n" + error.stack;
                    }

                    assert.fail(msg);
                }).then(function () {
                    // resolve the timeout Promise
                    at.resolve();
                    resolve();
                });
            } else {
                // if test does not return a Promise, simply clear the timeout
                // and resolve our test Promise
                at.resolve();
                resolve();
            }
        }).then(function () {
            // tell QUnit that the test is over so that it can clean up and start the next test
            done();
            console.log("Ending: " + name);
        });
    });
}

If a test times out, my timeout Promise will assert.fail() on the test so that the test is marked as failed, which is all well and good, but the test continues to run because the test Promise (result) is still waiting to resolve it.

I need a good way to cancel my test. I can do it by creating a field on the framework module this.cancelTest or something, and checking every so often (e.g. at the beginning of each then() iteration) within the test whether to cancel out. However, ideally, I could use $$(at).on("timeout", /* something here */) to clear the remaining then()s on my result variable, so that none of the rest of the test is run.

Does something like this exist?

Quick Update

I tried using Promise.race([result, at.promise]). It didn't work.

Update 2 + confusion

To unblock me, I added a few lines with the mod.cancelTest/polling within the test idea. (I also removed the event trigger.)

return new Promise(function (resolve, reject) {
    console.log("Beginning: " + name);

    var at = Promise.asyncTimeout(options.timeout, "Test timed out.");
    at.promise.catch(function () {
        // end the test if it times out
        mod.cancelTest = true;
        assert.fail("Test timed out");
        resolve();
    });

    // ...
    
}).then(function () {
    // tell QUnit that the test is over so that it can clean up and start the next test
    done();
    console.log("Ending: " + name);
});

I set a breakpoint in the catch statement, and it's being hit. What's confusing me now is that the then() statement isn't being called. Ideas?

Update 3

Figured the last thing out. fn.call() was throwing an error which I didn't catch, so the test promise was rejecting before at.promise.catch() could resolve it.

Community
  • 1
  • 1
dx_over_dt
  • 13,240
  • 17
  • 54
  • 102
  • It's possible to do cancellation with ES6 promises but it's not a property of the promise (rather - it's a property of the function that returns it) I can make a short example if you're interested. – Benjamin Gruenbaum Apr 07 '15 at 16:52
  • @BenjaminGruenbaum I know it's been nearly a year, but I'm still interested if you have the time to write an example. :) – dx_over_dt May 31 '16 at 16:19
  • 1
    It's been a year ago but it has been officially discussed two days before yesterday with cancellation tokens and cancellable promises moving to stage 1. – Benjamin Gruenbaum May 31 '16 at 16:51
  • 5
    The ES6 answer to cancelling a Promise is Observable. You can read more about this in here : https://github.com/Reactive-Extensions/RxJS – Frank Goortani Jan 26 '17 at 16:28
  • Linking [my answer](https://stackoverflow.com/a/53093799/1768303) on using the `Prex` library for promise cancellation. – noseratio Nov 01 '18 at 01:09

17 Answers17

104

Is there a method for clearing the .thens of a JavaScript Promise instance?

No. Not in ECMAScript 6 at least. Promises (and their then handlers) are uncancellable by default (unfortunately). There is a bit of discussion on es-discuss (e.g. here) about how to do this in the right way, but whatever approach will win it won't land in ES6.

The current standpoint is that subclassing will allow to create cancellable promises using your own implementation (not sure how well that'll work).

Until the language commitee has figured out the best way (ES7 hopefully?) you can still use userland Promise implementations, many of which feature cancellation.

Current discussion is in the https://github.com/domenic/cancelable-promise and https://github.com/bergus/promise-cancellation drafts.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 3
    "A bit of discussion" - I can link to maybe 30 threads on esdiscuss or GitHub :) (not to mention your own help with cancellation in bluebird 3.0) – Benjamin Gruenbaum Apr 07 '15 at 16:51
  • @BenjaminGruenbaum: Do you have those links ready to share somewhere? I've long wanted to summarize opinions and attempts and post a proposal to esdiscuss, so I'd be happy if I can check back that I forgot nothing. – Bergi Apr 07 '15 at 17:54
  • I have them handy at work - so I'll have them in 3-4 days. You can check the promise cancellation spec under promises-aplus for a good start. – Benjamin Gruenbaum Apr 07 '15 at 18:00
  • @BenjaminGruenbaum: Thanks, I can easily await that. Btw, promises-aplus is where I started :-) – Bergi Apr 07 '15 at 18:04
  • It's interesting what the Haskell folks write on the subject: [Not much](http://stackoverflow.com/questions/17677910/breaking-out-of-monad-sequence). There seems to be no particular interest. Maybe it can be avoided in the first place with a proper `Promise` chain design. When I compose normal functions I've never felt the need to break out early of the composition. Since `Promise` chains are just compositions of non-blocking functions... –  Jul 09 '16 at 20:26
  • 1
    @LUH3417: "normal" functions are just boring in that regard. You start a program and wait until it has finished - or you `kill` it and ignore in what possibly weird state the side effects have left your environment in (so you typically just throw away that as well, e.g. any half-finished outputs). Non-blocking or asynchronous functions however are built to work in interactive applications, where you want to have such kind of finer control over the execution of ongoing operations. – Bergi Jul 09 '16 at 21:29
  • @ftor one use case I found is if you want to keep track of the last promise added to the chain. In other words, the thenable equivalent of GIT branch HEAD. The only way I can think to do this, without running a separate counter, is to maintain a thenable pointer and un-plug it from the current head and plug it into to the new head when adding a link to the chain. As far as I can work out, his is not possible without the ability to unsubscribe. If you _could_ unsubscribe, then its very easy to make a promise-based FIFO queue with a drain event. – Cool Blue Oct 24 '16 at 06:55
  • 8
    Domenic [removed the TC39 proposal...](https://github.com/tc39/proposal-cancelable-promises)... cc @BenjaminGruenbaum – Sergio Dec 20 '16 at 12:03
60

While there isn't a standard way of doing this in ES6, there is a library called Bluebird to handle this.

There is also a recommended way described as part of the react documentation. It looks similar to what you have in your 2 and 3rd updates.

const makeCancelable = (promise) => {
  let hasCanceled_ = false;

  const wrappedPromise = new Promise((resolve, reject) => {
    promise.then((val) =>
      hasCanceled_ ? reject({isCanceled: true}) : resolve(val)
    );
    promise.catch((error) =>
      hasCanceled_ ? reject({isCanceled: true}) : reject(error)
    );
  });

  return {
    promise: wrappedPromise,
    cancel() {
      hasCanceled_ = true;
    },
  };
};

const cancelablePromise = makeCancelable(
  new Promise(r => component.setState({...}}))
);

cancelablePromise
  .promise
  .then(() => console.log('resolved'))
  .catch((reason) => console.log('isCanceled', reason.isCanceled));

cancelablePromise.cancel(); // Cancel the promise

Taken from: https://facebook.github.io/react/blog/2015/12/16/ismounted-antipattern.html

Michael Yagudaev
  • 6,049
  • 3
  • 48
  • 53
28

I'm really surprised that no-one mentions Promise.race as a candidate for this:

const actualPromise = new Promise((resolve, reject) => { setTimeout(resolve, 10000) });
let cancel;
const cancelPromise = new Promise((resolve, reject) => {
    cancel = reject.bind(null, { canceled: true })
})

const cancelablePromise = Object.assign(Promise.race([actualPromise, cancelPromise]), { cancel });
Pho3nixHun
  • 820
  • 2
  • 11
  • 24
  • 4
    I don't believe this works. If you change the promise to log, running `cancel()` will still result in the log being called. ``` const actualPromise = new Promise((resolve, reject) => { setTimeout(() => { console.log('actual called'); resolve() }, 10000) }); ``` – shmck Jun 11 '18 at 02:33
  • 7
    The question was how to cancel a promise (=> stop chained ```then```s to be executed), not how to cancel ```setTimeout``` (=>```clearTimeout```) or synchronous code, where unless you put an if after each line (```if (canceled) return```) this cannot be achieved. (Don't do this) – Pho3nixHun Jun 24 '18 at 20:21
  • 6
    Yeah, how this works is actually a little subtle, but it works. The `Promise` constructor runs synchronously so has already run, and can't be cancelled. It called `setTimeout` to schedule an event which will resolve the promise, **but that event is not part of the promise chain**. Calling `cancel` causes the cancellable promise to be rejected, so anything chained to run when it resolves will be discarded without being run. The `setTimeout` event handler still runs eventually but can't resolve an already rejected promise; if you want to cancel the timeout itself you need a different mechanism. – Ben Aug 27 '21 at 04:17
  • @ben I could imagine some top-level object that contains timer references that can be cleared in the `cancelPromise` code, but that's beyond the scope I think? – AncientSwordRage Dec 31 '21 at 14:33
24

Promise can be cancelled with the help of AbortController.

Is there a method for clearing then: yes you can reject the promise with AbortController object and then the promise will bypass all then blocks and go directly to the catch block.

Example:

import "abortcontroller-polyfill";

let controller = new window.AbortController();
let signal = controller.signal;
let elem = document.querySelector("#status")

let example = (signal) => {
    return new Promise((resolve, reject) => {
        let timeout = setTimeout(() => {
            elem.textContent = "Promise resolved";
            resolve("resolved")
        }, 2000);

        signal.addEventListener('abort', () => {
            elem.textContent = "Promise rejected";
            clearInterval(timeout);
            reject("Promise aborted")
        });
    });
}

function cancelPromise() {
    controller.abort()
    console.log(controller);
}

example(signal)
    .then(data => {
        console.log(data);
    })
    .catch(error => {
        console.log("Catch: ", error)
    });

document.getElementById('abort-btn').addEventListener('click', cancelPromise);

Html


    <button type="button" id="abort-btn" onclick="abort()">Abort</button>
    <div id="status"> </div>

Note: need to add polyfill, not supported in all browser.

Live Example

Edit elegant-lake-5jnh3

Sohail Ashraf
  • 10,078
  • 2
  • 26
  • 42
  • 1
    Also worth to be noted: fetch api call can optionally take a signal, allowing to abort the request. See https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal – PotatoesMaster Jan 29 '23 at 14:04
15
const makeCancelable = promise => {
    let rejectFn;

    const wrappedPromise = new Promise((resolve, reject) => {
        rejectFn = reject;

        Promise.resolve(promise)
            .then(resolve)
            .catch(reject);
    });

    wrappedPromise.cancel = () => {
        rejectFn({ canceled: true });
    };

    return wrappedPromise;
};

Usage:

const cancelablePromise = makeCancelable(myPromise);
// ...
cancelablePromise.cancel();
Slava M
  • 159
  • 1
  • 3
9

It is actually impossible to stop the execution of the promise, but you can hijack the reject and call it from the promise itself.

class CancelablePromise {
  constructor(executor) {
    let _reject = null;
    const cancelablePromise = new Promise((resolve, reject) => {
      _reject = reject;
      return executor(resolve, reject);
    });
    cancelablePromise.cancel = _reject;

    return cancelablePromise;
  }
}

Usage:

const p = new CancelablePromise((resolve, reject) => {
  setTimeout(() => {
    console.log('resolved!');
    resolve();
  }, 2000);
})

p.catch(console.log);

setTimeout(() => {
  p.cancel(new Error('Messed up!'));
}, 1000);
mfluehr
  • 2,832
  • 2
  • 23
  • 31
nikksan
  • 3,341
  • 3
  • 22
  • 27
  • 1
    @dx_over_dt Your edit would be a great comment, but not an edit. Please leave such substantive edits to the OP's purview (unless the post is marked as Community Wiki, of course). – TylerH Aug 12 '19 at 16:21
  • @TylerH so is the point of editing to fix typos and the like? Or to update information as it becomes outdated? I'm new to the ability to edit other people's posts privilege. – dx_over_dt Aug 12 '19 at 16:25
  • @dx_over_dt Yep, editing is to improve posts by fixing typos, grammatical errors, and to add syntax highlighting (if someone just posts a bunch of code but doesn't indent or tag it with ``` for example). Adding substantive content like additional explanations or reasoning/justifications for things is typically the purview of the person who posted the answer. You're free to suggest it in comments, and OP will be notified of the comment and can then respond to it, or they can just incorporate your suggestion into the post themselves. – TylerH Aug 12 '19 at 16:27
  • @dx_over_dt The exceptions are if a post is marked "Community Wiki" indicating it's intended to serve as a collaborative post (e.g. like Wikipedia), or if there are serious issues with the post such as rude/abusive language, dangerous/harmful content (e.g. suggestions or code that are liable to give you a virus or get you arrested, etc.), or personal info like health records, phone numbers, credit cards, etc.; feel free to remove those yourself. – TylerH Aug 12 '19 at 16:29
  • 1
    It's worth noting that the reason execution cannot be halted within a promise is that JavaScript is single-threaded. While the promise function is being executed, nothing else is running, so there is nothing to trigger halting the execution. – dx_over_dt Aug 12 '19 at 16:30
  • Would this work if `executor` returned a Promise with a `catch` in it? Suppose in my unit test scenario I expected an error and wanted to test that the feature would recover. Would calling `cancel` resume execution from that inner `catch`, or would it cancel the test as intended? – dx_over_dt Dec 16 '19 at 16:46
4

There are a few npm libraries for cancellable promises.

  1. p-cancelable https://github.com/sindresorhus/p-cancelable

  2. cancelable-promise https://github.com/alkemics/CancelablePromise

WebBrother
  • 1,447
  • 20
  • 31
3

Set a "cancelled" property on the Promise to signal then() and catch() to exit early. It's very effective, especially in Web Workers that have existing microtasks queued up in Promises from onmessage handlers.

// Queue task to resolve Promise after the end of this script
const promise = new Promise(resolve => setTimeout(resolve))

promise.then(_ => {
  if (promise.canceled) {
    log('Promise cancelled.  Exiting early...');
    return;
  }

  log('No cancelation signaled.  Continue...');
})

promise.canceled = true;

function log(msg) {
  document.body.innerHTML = msg;
}
anthumchris
  • 8,245
  • 2
  • 28
  • 53
  • 1
    Only one upvote? Man this answer is epic! 4.5 years old and the solution I needed (the others were too complicated for my simple use case). – codepleb Sep 05 '22 at 21:17
2

simple version:

just give out the reject function.

simple idea:

function MyPromise(myparams,cancel_holder) {
 return new Promise(function(resolve,reject){
  //do work here
  cancel_holder.cancel=reject
 }
}

or simple idea2:

function MyPromise() {
 var cancel_holder={};
 var promise=new Promise(function(resolve,reject){
  //do work here
  cancel_holder.cancel=reject;
 }
 promise.cancel=function(){ cancel_holder.cancel(); }
 return promise;
}

example:

function Sleep(ms,cancel_holder) {

 return new Promise(function(resolve,reject){
  var done=false; 
  var t=setTimeout(function(){if(done)return;done=true;resolve();}, ms);
  cancel_holder.cancel=function(){if(done)return;done=true;if(t)clearTimeout(t);reject();} 
 })
}

a wraper solution (factory)

the solution I found is to pass a cancel_holder object. it will have a cancel function. if it has a cancel function then it is cancelable.

This cancel function rejects the promise with Error('canceled').

Before resolve, reject, or on_cancel prevent the cancel function to be called without reason.

I have found convenient to pass the cancel action by injection

function cancelablePromise(cancel_holder,promise_fn,optional_external_cancel) {
  if(!cancel_holder)cancel_holder={};
  return new Promise( function(resolve,reject) {
    var canceled=false;
    var resolve2=function(){ if(canceled) return; canceled=true; delete cancel_holder.cancel; resolve.apply(this,arguments);}
    var reject2=function(){ if(canceled) return; canceled=true; delete cancel_holder.cancel; reject.apply(this,arguments);}
    var on_cancel={}
    cancel_holder.cancel=function(){
      if(canceled) return; canceled=true;

      delete cancel_holder.cancel;
      cancel_holder.canceled=true;

      if(on_cancel.cancel)on_cancel.cancel();
      if(optional_external_cancel)optional_external_cancel();

      reject(new Error('canceled'));
    };
    
    return promise_fn.call(this,resolve2,reject2,on_cancel);        
  });
}
 
function Sleep(ms,cancel_holder) {

 return cancelablePromise(cancel_holder,function(resolve,reject,oncacnel){

  var t=setTimeout(resolve, ms);
  oncacnel.cancel=function(){if(t)clearTimeout(t);}     

 })
}


let cancel_holder={};

// meanwhile in another place it can be canceled
setTimeout(function(){  if(cancel_holder.cancel)cancel_holder.cancel(); },500) 

Sleep(1000,cancel_holder).then(function() {
 console.log('sleept well');
}, function(e) {
 if(e.message!=='canceled') throw e;
 console.log('sleep interrupted')
})
Shimon Doodkin
  • 4,310
  • 34
  • 37
1

Here's our implementation https://github.com/permettez-moi-de-construire/cancellable-promise

Used like

const {
  cancellablePromise,
  CancelToken,
  CancelError
} = require('@permettezmoideconstruire/cancellable-promise')

const cancelToken = new CancelToken()

const initialPromise = SOMETHING_ASYNC()
const wrappedPromise = cancellablePromise(initialPromise, cancelToken)


// Somewhere, cancel the promise...
cancelToken.cancel()


//Then catch it
wrappedPromise
.then((res) => {
  //Actual, usual fulfill
})
.catch((err) => {
  if(err instanceOf CancelError) {
    //Handle cancel error
  }

  //Handle actual, usual error
})

which :

  • Doesn't touch Promise API
  • Let us make further cancellation inside catch call
  • Rely on cancellation being rejected instead of resolved unlike any other proposal or implementation

Pulls and comments welcome

Cyril CHAPON
  • 3,556
  • 4
  • 22
  • 40
1

Try promise-abortable: https://www.npmjs.com/package/promise-abortable

$ npm install promise-abortable
import AbortablePromise from "promise-abortable";

const timeout = new AbortablePromise((resolve, reject, signal) => {
  setTimeout(reject, timeToLive, error);
  signal.onabort = resolve;
});

Promise.resolve(fn()).then(() => {
  timeout.abort();
});
Devi
  • 27
  • 2
1

I am still working through this idea, but here is how I have implemented a cancellable Promise using setTimeout as an example.

The idea is that a promise is resolved or rejected whenever you have decided it is, so it should be a matter of deciding when you want to cancel, satisfying the criterion, and then calling the reject() function yourself.

  • First, I think there are two reasons to finish a promise early: to get it over and done with (which I have called resolve) and to cancel (which I have called reject). Of course, that’s just my feeling. Of course there is a Promise.resolve() method, but it’s in the constructor itself, and returns a dummy resolved promise. This instance resolve() method actually resolves an instantiated promise object.

  • Second, you can happily add anything you like to a newly created promise object before you return it, and so I have just added resolve() and reject() methods to make it self-contained.

  • Third, the trick is to be able to access the executor resolve and reject functions later, so I have simply stored them in a simple object from within the closure.

I think the solution is simple, and I can’t see any major problems with it.

function wait(delay) {
  var promise;
  var timeOut;
  var executor={};
  promise=new Promise(function(resolve,reject) {
    console.log(`Started`);
    executor={resolve,reject};  //  Store the resolve and reject methods
    timeOut=setTimeout(function(){
      console.log(`Timed Out`);
      resolve();
    },delay);
  });
  //  Implement your own resolve methods,
  //  then access the stored methods
      promise.reject=function() {
        console.log(`Cancelled`);
        clearTimeout(timeOut);
        executor.reject();
      };
      promise.resolve=function() {
        console.log(`Finished`);
        clearTimeout(timeOut);
        executor.resolve();
      };
  return promise;
}

var promise;
document.querySelector('button#start').onclick=()=>{
  promise=wait(5000);
  promise
  .then(()=>console.log('I have finished'))
  .catch(()=>console.log('or not'));
};
document.querySelector('button#cancel').onclick=()=>{ promise.reject(); }
document.querySelector('button#finish').onclick=()=>{ promise.resolve(); }
<button id="start">Start</button>
<button id="cancel">Cancel</button>
<button id="finish">Finish</button>
Manngo
  • 14,066
  • 10
  • 88
  • 110
0

@Michael Yagudaev 's answer works for me.

But the original answer did not chain the wrapped promise with .catch() to handle reject handling, here is my improvement on top of @Michael Yagudaev's answer:

const makeCancelablePromise = promise => {
  let hasCanceled = false;
  const wrappedPromise = new Promise((resolve, reject) => {
    promise
      .then(val => (hasCanceled ? reject({ isCanceled: true }) : resolve(val)))
      .catch(
        error => (hasCanceled ? reject({ isCanceled: true }) : reject(error))
      );
  });

  return {
    promise: wrappedPromise,
    cancel() {
      hasCanceled = true;
    }
  };
};

// Example Usage:
const cancelablePromise = makeCancelable(
  new Promise((rs, rj) => {
    /*do something*/
  })
);
cancelablePromise.promise.then(() => console.log('resolved')).catch(err => {
  if (err.isCanceled) {
    console.log('Wrapped promise canceled');
    return;
  }
  console.log('Promise was not canceled but rejected due to errors: ', err);
});
cancelablePromise.cancel();
0

If p is a variable that contains a Promise, then p.then(empty); should dismiss the promise when it eventually completes or if it is already complete (yes, I know this isn't the original question, but it is my question). "empty" is function empty() {} . I'm just a beginner and probably wrong, but these other answers seem too complicated. Promises are supposed to be simple.

David Spector
  • 1,520
  • 15
  • 21
0

If your code is placed in a class you could use a decorator for that. You have such decorator in the utils-decorators (npm install --save utils-decorators). It will cancel the previous invocation of the decorated method if before the resolving of the previous call there was made another call for that specific method.

import {cancelPrevious} from 'utils-decorators';

class SomeService {

   @cancelPrevious()
   doSomeAsync(): Promise<any> {
    ....
   }
}

or you could use a wrapper function:

import {cancelPreviousify} from 'utils-decorators';

const cancelable = cancelPreviousify(originalMethod)

https://github.com/vlio20/utils-decorators#cancelprevious-method

vlio20
  • 8,955
  • 18
  • 95
  • 180
-1

If you want to stop all thens/catchs from being executed you can do this by injecting a promise that will never resolve. It probably has memory leak reprocusions but it will fix the issue and shouldn't cause too much wasted memory in most applications.

new Promise((resolve, reject) => {
    console.log('first chain link executed')
    resolve('daniel');
}).then(name => {
    console.log('second chain link executed')
    if (name === 'daniel') {
        // I don't want to continue the chain, return a new promise
        // that never calls its resolve function
        return new Promise((resolve, reject) => {
            console.log('unresolved promise executed')
        });
    }
}).then(() => console.log('last chain link executed'))

// VM492:2 first chain link executed
// VM492:5 second chain link executed
// VM492:8 unresolved promise executed
DanLatimer
  • 117
  • 8
-2

Using CPromise package we can use the following approach (Live demo)

import CPromise from "c-promise2";

const chain = new CPromise((resolve, reject, { onCancel }) => {
  const timer = setTimeout(resolve, 1000, 123);
  onCancel(() => clearTimeout(timer));
})
  .then((value) => value + 1)
  .then(
    (value) => console.log(`Done: ${value}`),
    (err, scope) => {
      console.warn(err); // CanceledError: canceled
      console.log(`isCanceled: ${scope.isCanceled}`); // true
    }
  );

setTimeout(() => {
  chain.cancel();
}, 100);

The same thing using AbortController (Live demo)

import CPromise from "c-promise2";

const controller= new CPromise.AbortController();

new CPromise((resolve, reject, { onCancel }) => {
  const timer = setTimeout(resolve, 1000, 123);
  onCancel(() => clearTimeout(timer));
})
  .then((value) => value + 1)
  .then(
    (value) => console.log(`Done: ${value}`),
    (err, scope) => {
      console.warn(err);
      console.log(`isCanceled: ${scope.isCanceled}`);
    }
  ).listen(controller.signal);

setTimeout(() => {
  controller.abort();
}, 100);
Dmitriy Mozgovoy
  • 1,419
  • 2
  • 8
  • 7