Getting the latest data from a promise returning service called repeatedly

生来就可爱ヽ(ⅴ<●) 提交于 2019-11-28 05:11:22

问题


I have an angular service that returns a promise for data obtained from a server. This data is requested multiple times in a fixed interval to keep the display up to date. The response can sometimes be quite slow (up to 10 seconds), and if two requests overlap and the first one responds last I'll get out of date information displayed in the app. The timelines would be something like:

- first request
Req ---------------------> Res
- second request
      Req -------> Res

Currently I keep a counter of the request and make the .then function close over it and discard the data if it is too old. I wonder if one of the promise libraries out there already does it or if there is a standard way to do it.

I've also though about adding the response timestamp to the returned object, or somehow using something like RxJs but I haven't used it to know if it applies somehow.


回答1:


TL&DR: We are inventing cancellable promises here.

Well.. OK. Some infrastructure. This is a typical example where you really need Promise.cancel() However we don't have it in ES6 native promises. Being a library agnostic person i just go ahead and invent one by Promise sub-classing.

The following function takes a promise and makes it cancellable by adding a non-enumerable and non-configurable property called __cancelled__ It also adds .then() and .cancel() methods to it's property chain without modifying the Promise.prototype. Since cancellable promise object's proptotype's prototype is Promise.prototype, our cancellable promise has access to all Promise thingies. Ah.. before i forget; cancellable prototype's then method also returns a cancellable promise.

function makePromiseCancellable(p){
  Object.defineProperty(p,"__cancelled__", {        value: false,
                                                 writable: true,
                                               enumerable: false,
                                             configurable: false
                                           });
  Object.setPrototypeOf(p,makePromiseCancellable.prototype);
  return p;
}

makePromiseCancellable.prototype = Object.create(Promise.prototype);
makePromiseCancellable.prototype.then   = function(callback){
                                            return makePromiseCancellable(Promise.prototype.then.call(this,function(v){
                                                                                                             !this.__cancelled__ && callback(v);
                                                                                                           }.bind(this)));
                                          };
makePromiseCancellable.prototype.cancel = function(){
                                            this.__cancelled__ = true;
                                            return this;
                                          };

So we have a utility function called getAsyncData() which returns us a standard ES6 promise which resolves in 2000 msecs. We will obtain two promises from this function, and turn them into cancellable promises called cp0 and cp1. Then we will cancel cp0 at 1000 msecs and see what happens.

function getAsyncData(){
  var dur = 2000;
  return new Promise((v,x) => setTimeout(v.bind(this,"promise id " + pid++ + " resolved at " + dur + " msec"),dur));
}

function makePromiseCancellable(p){
  Object.defineProperty(p,"__cancelled__", {        value: false,
                                                 writable: true,
                                               enumerable: false,
                                             configurable: false
                                           });
  Object.setPrototypeOf(p,makePromiseCancellable.prototype);
  return p;
}

makePromiseCancellable.prototype = Object.create(Promise.prototype);
makePromiseCancellable.prototype.then   = function(callback){
                                            return makePromiseCancellable(Promise.prototype.then.call(this,function(v){
                                                                                                             !this.__cancelled__ && callback(v);
                                                                                                           }.bind(this)));
                                          };
makePromiseCancellable.prototype.cancel = function(){
                                            this.__cancelled__ = true;
                                          };
var pid = 0,
    cp0 = makePromiseCancellable(getAsyncData());
    cp1 = makePromiseCancellable(getAsyncData());
cp0.then(v => console.log(v));
cp1.then(v => console.log(v));

setTimeout(_ => cp0.cancel(),1000);

Wow..! fantastic. cp1 resolved at 2000 msec while cp0 has got cancelled at 1000 msecs.

Now, since we now have the infrastructure, we can use it to solve your problem.

The following is the code that we will use;

function getAsyncData(){
  var dur = ~~(Math.random()*9000+1001);
  return new Promise((v,x) => setTimeout(v.bind(this,"promise id " + pid++ + " resolved at " + dur + " msec"),dur));
}

function runner(fun,cb){
  var promises = [];
  return setInterval(_ => { var prom = makePromiseCancellable(fun());
                            promises.push(prom);
                            promises[promises.length-1].then(data => { promises.forEach(p => p.cancel());
                                                                       promises.length = 0;
                                                                       return cb(data);
                                                                     });
                          },1000);
}

var pid = 0,
    sid = runner(getAsyncData,v => console.log("received data:", v));
setTimeout(_=> clearInterval(sid),60001);

It's pretty basic. The runner() function is doing the job. It's requesting a promise every 1000msecs by invoking getAsyncData(). The getAsyncData() function however this time will give us a promise which will resolve in 1~10 seconds. This is so because we want some of the later promises to be able to resolve while some of the previously received ones are still in unresolved state. Just like in your case. OK; after making the received promise cancellable, the runner() function pushes it into the promises array. Only after pushing the promise to the promises array we attach the then instruction to it because we want the array to hold only the main promises, not the ones returned from the then stage. Which ever promise resolves first and calls it's then method, will first cancel all the promises in the array and then empty the array; only after that will invoke the provided callback function.

So now let's see the whole thing in action.

function makePromiseCancellable(p){
  Object.defineProperty(p,"__cancelled__", {        value: false,
                                                 writable: true,
                                               enumerable: false,
                                             configurable: false
                                           });
  Object.setPrototypeOf(p,makePromiseCancellable.prototype);
  return p;
}

makePromiseCancellable.prototype = Object.create(Promise.prototype);
makePromiseCancellable.prototype.then   = function(callback){
                                            return makePromiseCancellable(Promise.prototype.then.call(this,function(v){
                                                                                                             !this.__cancelled__ && callback(v);
                                                                                                           }.bind(this)));
                                          };
makePromiseCancellable.prototype.cancel = function(){
                                            this.__cancelled__ = true;
                                            return this;
                                          };

function getAsyncData(){
  var dur = ~~(Math.random()*9000+1001);
  return new Promise((v,x) => setTimeout(v.bind(this,"promise id " + pid++ + " resolved at " + dur + " msec"),dur));
}

function runner(fun,cb){
  var promises = [];
  return setInterval(_ => { var prom = makePromiseCancellable(fun());
                            promises.push(prom);
                            promises[promises.length-1].then(data => { promises.forEach(p => p.cancel());
                                                                       promises.length = 0;
                                                                       return cb(data);
                                                                     });
                          },1000);
}

var pid = 0,
    sid = runner(getAsyncData,v => console.log("received data:", v));
setTimeout(_=> clearInterval(sid),60001);

The runner function will run indefinitelly if you don't stop it. So at 60001msecs I clear it by a clearInterval(). During that period 60 promises will be received and only the first resolvers will invoke the provided callback by cancelling all the previous currently received promises, including the still unresolved ones, those received after the first resolving promise in our promises array. However since those later promises are expected to contain more fresh data, one might want to keep them uncancelled. Then I suppose the following small change in the code will do better in terms of refreshing the screen more frequently with the latest data.

function makePromiseCancellable(p){
  Object.defineProperty(p,"__cancelled__", {        value: false,
                                                 writable: true,
                                               enumerable: false,
                                             configurable: false
                                           });
  Object.setPrototypeOf(p,makePromiseCancellable.prototype);
  return p;
}

makePromiseCancellable.prototype = Object.create(Promise.prototype);
makePromiseCancellable.prototype.then   = function(callback){
                                            return makePromiseCancellable(Promise.prototype.then.call(this,function(v){
                                                                                                             !this.__cancelled__ && callback(v);
                                                                                                           }.bind(this)));
                                          };
makePromiseCancellable.prototype.cancel = function(){
                                            this.__cancelled__ = true;
                                            return this;
                                          };

function getAsyncData(){
  var dur = ~~(Math.random()*9000+1001);
  return new Promise((v,x) => setTimeout(v.bind(this,"promise id " + pid++ + " resolved at " + dur + " msec"),dur));
}

function runner(fun,cb){
  var promises = [];
  return setInterval(_ => { var prom = makePromiseCancellable(fun());
                            promises.push(prom);
                            promises[promises.length-1].then(data => { var prix = promises.indexOf(prom);
                                                                       promises.splice(0,prix)
                                                                               .forEach(p => p.cancel());
                                                                       return cb(data);
                                                                     });
                          },1000);
}

var pid = 0,
    sid = runner(getAsyncData,v => console.log("received data:", v));
setTimeout(_=> clearInterval(sid),60001);

There might be some flaws of course. I would like to hear your ideas.



来源:https://stackoverflow.com/questions/39824408/getting-the-latest-data-from-a-promise-returning-service-called-repeatedly

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!