Skip to content Skip to sidebar Skip to footer

How To Implement Cancellable, Ordered Promises?

I've put together an example to demonstrate what I'm getting at: Type in the 'search' box and watch the console. The search results come back out of order! How can we cancel any p

Solution 1:

Instead of using debounce, or timeouts, I set a small amount of state outside inside (suggestion by Jaromanda X) of this function that uses a referenced function. This way, you can just change the function reference to something like a noop. The promise still resolves, but it won't take any action. However, the final one will not have changed its function reference:

var onInput = function() {
  let logger = function(term, results) {
    console.log(`results for "${term}"`, results);
  };
  let noop = Function.prototype;
  let lastInstance = null;

  functionActionManager(action) {
    this.action = action;
  }

  returnfunctiononInput(ev) {
    let term = ev.target.value;
    console.log(`searching for "${term}"`);

    if (lastInstance) {
      lastInstance.action = noop;
    }

    let inst = newActionManager(logger.bind(null, term));
    lastInstance = inst;

    getSearchResults(term).then(response => inst.action(response));
  }
}();



/****************************************
 * The rest of the JavaScript is included only for simulation purposes
 ****************************************/functiongetSearchResults(term) {
  returnnewPromise((resolve, reject) => {
    let timeout = getRandomIntInclusive(100, 2000);
    setTimeout(() => {
      resolve([term.toLowerCase(), term.toUpperCase()]);
    }, timeout);

  });
}

functiongetRandomIntInclusive(min, max) {
  min = Math.ceil(min);
  max = Math.floor(max);
  returnMath.floor(Math.random() * (max - min + 1)) + min;
}
<inputonInput="onInput(event)">

Solution 2:

You can use Promise.race to cancel the effect of a previous chain:

letcancel = () => {};

functiononInput(ev) {
  let term = ev.target.value;
  console.log(`searching for "${term}"`);
  cancel();
  let p = newPromise(resolve => cancel = resolve);
  Promise.race([p, getSearchResults(term)]).then(results => {
    if (results) {
      console.log(`results for "${term}"`,results);
    }
  });
}

functiongetSearchResults(term) {
  returnnewPromise(resolve => {
    let timeout = 100 + Math.floor(Math.random() * 1900);
    setTimeout(() =>resolve([term.toLowerCase(), term.toUpperCase()]), timeout);
  });
}
<inputonInput="onInput(event)">

Here we're doing it by injecting an undefined result and testing for it.

Solution 3:

One workable solution is to include a latestTimestamp and simply ignore any responses that come in with an early timestamp (and and therefore obsolete).

let latestTimestamp = 0;

functiononInput(ev) {
  let term = ev.target.value;
  console.log(`searching for "${term}"`);
  latestTimestamp = Date.now();
  getSearchResults(term, latestTimestamp).then(results => {
    if (results[2] !== latestTimestamp) {
      console.log("Ignoring old answer");
    } else {
      console.log(`results for "${term}"`, results);
    }
  });
}

functiongetSearchResults(term, latestTimestamp) {
  returnnewPromise((resolve, reject) => {
    let timeout = getRandomIntInclusive(100, 2000);
    setTimeout(() => {
      resolve([term.toLowerCase(), term.toUpperCase(), latestTimestamp]);
    }, timeout);

  });
}

functiongetRandomIntInclusive(min, max) {
  min = Math.ceil(min);
  max = Math.floor(max);
  returnMath.floor(Math.random() * (max - min + 1)) + min;
}
<inputonInput="onInput(event)">

Solution 4:

You shouldn't use setTimeout's in promises the way you are doing it, because from the .then you are returning the callback from the .setTimeout() which would not work and mess up the order. To make the promises go in order you should make a function like shown below:

functionwait(n){
    returnnewPromise(function(resolve){
        setTimeout(resolve, n)
    });
}

and substitute the setTimeout()'s with that function like shown below:

wait(getRandomIntInclusive(100,2000)).then(function(){
    // code
});

Solution 5:

You can use async package - a bunch of utilities to maintain asynchronous code. It was first developed for node.js but it can also be used in frontend. You need series function, it saves an order of promises. Here is a brief example in coffeescript:

async.series([
  ->
    ### do some stuff ###
    Q 'one'
  ->
    ### do some more stuff ... ###
    Q 'two'
]).then (results) ->
    ### results is now equal to ['one', 'two'] ###
    doStuff()
  .done()

### an example using an object instead of an array ###
async.series({
  one: -> Q.delay(200).thenResolve(1)
  two: -> Q.delay(100).thenResolve(2)
}).then (results) ->
    ### results is now equal to: {one: 1, two: 2} ###
    doStuff()
  .done()  

See caolan.github.io/async/

Post a Comment for "How To Implement Cancellable, Ordered Promises?"