简体   繁体   中英

javascript/jquery: Iterative called function; wait till the previous call is finished

I've some problem with a library calling a function on each item. I've to check the state for this item via an ajax request and don't want to call one request per item, but get a range of item states. Because these items are dates I can get some range pretty easy - that's the good part :)

So to to give some code ...

var itemStates = {};

var libraryObj = {
    itemCallback: function(item) {
        return checkState(item);
    }
}

function checkState(item) {
    if(!itemStates.hasOwnProperty(item)) {
        $.get('...', function(result) {
            $.extend(true, itemStates, result);
        });
    }

    return itemStates[item];
}

The library is now calling library.itemCallback() on each item, but I want to wait for the request made in checkState() before calling checkState() again (because the chance is extremly high the next items' state was allready requested within the previous request.

I read about the defer and wait(), then() and so on, but couldn't really get an idea how to implement this.

Many thanks to everybody who could help me with this :)

simplest and dirty way of taking control over the library is to override their methods

But I don't really know core problem here so other hints are below

If you have the control over the checkState then just collect your data and change your controller on the server side to work with arrays that's it

and if you don't know when the next checkState will be called to count your collection and make the request use setTimeout to check collection after some time or setIterval to check it continuously

if you don't want to get same item multiple times then store your checked items in some variable like alreadyChecked and before making request search for this item in alreadyChecked

to be notified when some library is using your item use getter , and then collect your items. When you will have enough items collected then you can make the request, but when you will not have enought items then use setTimeout and wait for some time. If nothing changes, then it means that library finishes the iteration for now and you can make the request with items that left of.

let collection=[];// collection for request
let _items={};// real items for you when you don't want to perfrom actions while getting values
let itemStates={};// items for library
let timeoutId;
//instead of itemStates[someState]=someValue; use
function setItem(someState,someValue){

  Object.defineProperty(itemStates, someState, { get: function () {
    if(typeof timeoutId=="number")clearTimeout(timeoutId);
    //here you can add someState to the collection for request
    collection.push(_items[someState]);
    if(collection.length>=10){
      makeRequest();
    }else{
      timeoutId=setTimeout(()=>{...checkCollectionAndMakeRequest...},someTime);
    }
    return someValue;
  } });
}

The library is now calling library.itemCallback() on each item, but I want to wait for the request made in checkState() before calling checkState() again (because the chance is extremely high the next items' state was already requested within the previous request.

One thing I can think of doing is making some caching function, depending on the last time the function was called return the previous value or make a new request

var cached = function(self, cachingTime, fn){
    var paramMap = {};

    return function( ) {

        var arr = Array.prototype.slice.call(arguments);
        var parameters = JSON.stringify(arr);
        var returning;

        if(!paramMap[parameters]){

           returning = fn.apply(self,arr);       
           paramMap[parameters]={timeCalled: new Date(), value:returning};

        } else  {

           var diffMs = Math.abs(paramMap[parameters].timeCalled - new Date());
           var diffMins = ( diffMs / 1000 ) / 60;

           if(diffMins > cachingTime){

               returning = fn.apply(self,arr);       
               paramMap[parameters] = {timeCalled: new Date(), value:returning};    

           } else {

              returning = paramMap[parameters].value;

            }

        }

        return returning;
    }
}

Then you'd wrap the ajax call into the function you've made

var fn = cached(null, 1 , function(item){
        return $.get('...', function(result) {
            $.extend(true, itemStates, result);
        });
});

Executing the new function would get you the last promise called for those parameters within the last request made at the last minute with those parameters or make a new request

You can achieve this by using jQuery.Deferred or Javascript Promise . In the following code, itemCallback() will wait for previous calls to finish before calling checkState() .

var queue = [];
var itemStates = {};

var libraryObj = {
  itemCallback: function(item) {
    var def = $.Deferred();
    $.when.apply(null, queue)
      .then(function() {
        return checkState(item);
      })
      .then(function(result) {
        def.resolve(result);
      });
    queue.push(def.promise());
    return def.promise();
  }
}

function checkState(item) {
  var def = $.Deferred();
  if (!itemStates.hasOwnProperty(item)) {
    $.get('...', function(result) {
      $.extend(true, itemStates, result);
      def.resolve(itemStates[item]);
    });
  } else
    def.resolve(itemStates[item]);
  return def.promise();
}

//these will execute in order, waiting for the previous call
libraryObj.itemCallback(1).done(function(r) { console.log(r); });
libraryObj.itemCallback(2).done(function(r) { console.log(r); });
libraryObj.itemCallback(3).done(function(r) { console.log(r); });
libraryObj.itemCallback(4).done(function(r) { console.log(r); });
libraryObj.itemCallback(5).done(function(r) { console.log(r); });

Same example built with Javascript Promises

var queue = [];
var itemStates = {};

var libraryObj = {
  itemCallback: function(item) {
    var promise = new Promise(resolve => {
      Promise.all(queue)
        .then(() => checkState(item))
        .then((result) => resolve(result));
    });
    queue.push(promise);
    return promise;
  }
}

function checkState(item) {
  return new Promise(resolve => {
    if (item in itemStates)
      resolve(itemStates[item]);
    else {
      $.get('...', function(result) {
        $.extend(true, itemStates, result);
        resolve(itemStates[item]);
      });
    }
  });
}

//these will execute in order, waiting for the previous call
libraryObj.itemCallback(1).then(function(r) { console.log(r); });
libraryObj.itemCallback(2).then(function(r) { console.log(r); });
libraryObj.itemCallback(3).then(function(r) { console.log(r); });
libraryObj.itemCallback(4).then(function(r) { console.log(r); });
libraryObj.itemCallback(5).then(function(r) { console.log(r); });

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM