最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Cancel a promise chain? - Stack Overflow

programmeradmin1浏览0评论

I chain a series of promises:

this.getData.then(this.getMoreData).then(this.getEvenMoreData);

At some point the user may decide to cancel the request and request something else.

How can I cancel the propagation of the chain?

I chain a series of promises:

this.getData.then(this.getMoreData).then(this.getEvenMoreData);

At some point the user may decide to cancel the request and request something else.

How can I cancel the propagation of the chain?

Share Improve this question edited Jun 22, 2015 at 19:29 Paul Nikonowicz 3,90322 silver badges39 bronze badges asked Jun 22, 2015 at 19:11 panthropanthro 24.1k70 gold badges201 silver badges349 bronze badges 8
  • 1 What do you mean "the user"? This is a piece of code that's executed as quickly as possible, how would you add user intervention here? – tadman Commented Jun 22, 2015 at 19:12
  • The site is getting alot of data. If the user then requests other data, I want to halt getting the current set of data and outputting it. – panthro Commented Jun 22, 2015 at 19:12
  • So how would that play out? Is there a state object for this request that will indicate it was cancelled, like var state = { running: true } and then you can test if (state.running) { return this.getMoreData() } as necessary. – tadman Commented Jun 22, 2015 at 19:13
  • If you get your data via ajax you can cancel the request by using the abort Method on the XMLHttpRequest object. – Nick Russler Commented Jun 22, 2015 at 19:13
  • in case you use bluebird promises have a look at: github./petkaantonov/bluebird/blob/master/… . Basically the cancellation just works my throwing a custom object you can then catch – Safari Commented Jun 22, 2015 at 19:14
 |  Show 3 more ments

3 Answers 3

Reset to default 8

You'd have to check for the state (of whether you should cancel or not) inside each chained method:

var userRequestedCancel = false;

this
   .getData()
   .then(function() {
     if(userRequestedCancel) {
      return Promise.reject('user cancelled');
     }

     return getMoreData();
   })
   .then(function() {
     if(userRequestedCancel) {
      return Promise.reject('user cancelled');
     }

     return getEvenMoreData();
   })

Or perhaps a slightly more elegant way (edited to pass context and arguments to callback methods)

var currentReq = false;
var userRequestedCancel = false;
var shouldContinue = function(cb,args) {
    if(userRequestedCancel) {
        return Promise.reject('user cancelled');
    }

    currentReq = cb.apply(this,args);
    return currentReq;
}

var onCancel = function() {
    userRequestedCancel = true;
    currentReq && currentReq.abort();
}

this
   .getData()
   .then(function() {
    return shouldContinue(getMoreData,arguments);
   })
   .then(function() {
     return shouldContinue(getEvenMoreData,arguments);
   })

If you need to cancel the current request as well, that is kind of trivial, set your current ajax request to be a global variable, and whatever event sets the userRequestedCancel flag to true, have that also cancel the ajax request (see edited code above)

In order to cancel a promise chain you need to throw an error. Just have a look at the code below

function CancelError() {
    this.message = 'Cancelled';
}


obj
    .then(function() {
        throw new CancelError();
    })
    .catch(function(err) {
        if (err instanceof CancelError) {
            // Promise got cancelled
        }
        throw err; // throw the other mistakes
    });

Fun little challenge!

Without knowing exactly which task, request or process you're launching AND how the user can interrupt the said process, it's difficult to remend any solution to "break" a .then(...) chain without doing some hack / trick that will trigger the .catch(...) rejection callback.

That being said, see if this example sheds some light at all.

Pay particular attention to the makeInterruptablePromise function and how it's used:

var bar = $('.progress-bar');
var h3 = $("h3");
var isEscape;

function log(msg, replace) {
  h3[replace ? 'html' : 'append'](msg + "<br/>");
}

$(document).keydown(e => {
  switch(e.keyCode) {
    case 27: //ESCAPE
      return isEscape = true;
    case 32: //SPACE
      return runDemo();
  }
});

function makeInterruptablePromise(cbStatus) {
  return new Promise((resolve, reject) => {
    function loop() {
      switch(cbStatus()) {
        case 1: return resolve();
        case -1: return reject();
        default: requestAnimationFrame(loop);
      }
    }
    //Don't forget to start the loop!
    loop();
  })
}


function runDemo() {
  log("Wait for it... (ESC to interrupt, SPACE to replay)", true);
  
  isEscape = false;
  
  var timeToComplete = 2000;
  var timeStart = Date.now();
  
  function updateBar() {
    var timeDiff = Date.now() - timeStart;
    var timePercent = timeDiff / timeToComplete;
    TweenMax.set(bar, {scaleX: 1 - timePercent});
    return timePercent > 1;
  }
  
  makeInterruptablePromise(() => {
    if(isEscape) return -1;
    if(updateBar()) return 1;

    return 0;
  })
    .then(() => log("Inside *then* chain."))
    .catch(() => log("Skipped *then* chain!"))
}

runDemo(); //Run first time.
body {
  background-color: #123456;
  color: #fff;
}

.progress-bar {
  display: block;
  width: 200px;
  height: 10px;
  background-color: #88f;
  transform-origin: top left;
}
<script src="https://cdnjs.cloudflare./ajax/libs/gsap/1.20.2/TweenMax.min.js"></script>
<script src="https://ajax.googleapis./ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div class="progress-bar"></div>

<h3></h3>

What this essentially boils down to, is I'm passing a callback to makeInterruptablePromise to "monitor" for 3 possible statuses.

  • If it's 1: It resolves (goes into your 'then')
  • If it's -1: It rejects (skips to your 'catch')
  • Otherwise, it just keeps looping by using the browser's requestAnimationFrame(...) method.
    (essentially a setTimeout(...) calibrated to trigger per screen refreshes).

Now, to affect how these statuses changes over time, I demonstrated this by using ESCAPE as the interrupt status (-1) and a timer that runs for 2 seconds. Once plete, the timer returns status (1).

Not sure it can fit your need, but could be useful for anyone else trying to break Promises via some external / asynchronous factor.

发布评论

评论列表(0)

  1. 暂无评论