How to set callback order invocation same as targe

2019-03-06 17:29发布

问题:

I have a problem in my project.

To describe this issue I have wrote simplified code snippet:

function waitFor(fnReady, fnCallback) {
    var check = function() {
        if (fnReady()) {
            fnCallback();
        }
        else {
            setTimeout(check, 100);  // wait another 100ms, and try again
        }
    };

    check();
}


var result = 0;
var flag = true;
function ajaxRequest() {
    setTimeout(
         function() { flag = false; 
                     console.log('ping');
                    },3000
    );
}

function ajaxRequestHandler() {
    setTimeout(
         function() { flag = true; 
                      console.log('pong');
                    }, 200
    );
}
for(var i =0;i<10; i++){   
    waitFor(function() { return flag; }, ajaxRequest);
    waitFor(function() { return !flag; }, ajaxRequestHandler);
}

it returns:

ping - 10 times
pong - 10 times

desired result:

ping 
3 second timeout 
ping
---------------------
ping
3 second timeout 
pong
--------------------
.....

Can you help correct my code?

UPDATE

Actual problem:

I have a google map.
I have a lot of places when I should to redraw it.

For application logic very important that If I send

request1
request2
request3
request4

I should handle responses in the this order

handle response of request1
handle response of request2
handle response of request3
handle response of request4 

Problem that I don't know order of requests.

In different places of file I see following code rows:

google.maps.event.addListener(searchBox, 'bounds_changed', renderTerminalsOnMapAndFitBounds);
...
$.getJSON('getAllTerminals.json', renderTerminalsOnMapAndFitBounds);
.....
$.getJSON('getAllTerminalsInsideRectangle.json', renderTerminalsOnMapAndFitBounds);
...
$.getJSON('getAllTerminalsInsideCircle.json', renderTerminalsOnMapAndFitBounds);
...
$.getJSON('getBigTerminals.json', renderTerminalsOnMapAndFitBounds);
........

renderTerminalsOnMapAndFitBounds method sends request to server and in succes alternative render result on map. But this event happens very often

回答1:

Try this pattern

var map = "abcdefghi".split("");
var responses = []; // collect responses
$.ajaxSetup({
    beforeSend : function(jqxhr, settings) {
      jqxhr.id = Number(settings.data.split(/id=/)[1]); // add `id` to `request`
        console.log(settings.data.split(/id=/)[1]);
    }
});
var request = function(id, data) {
    // append `id` to `id` data
    return $.post("/echo/json/", {json:JSON.stringify([data]), id:id})
};

$.each(map, function(k, v) {
    setTimeout(function() {
      request(k + 1, v)
      .done(function(data) {
        // do stuff at each response
        console.log(data); // note return values
      })
      .always(function(data, textStatus, jqxhr) {
          // do stuff at each response
          responses.push([jqxhr.id, data[0]]);
          // do stuff when all requests completed , results items in `responses`
          if (responses.length === map.length) {
              responses.sort(); // sort `responses` based on `id`
              // do stuff with `responses`
              console.log(responses);
          }
      });
    },1 + Math.random() * 1000) // async
});

jsfiddle http://jsfiddle.net/guest271314/g254bbjg/



回答2:

my variant:

var index = 0;
// callback function
function tryMe (param1) { 
    waitFor(function(){return param1 == index}, 
            function(){console.log(param1);
                       index++;
                      }
    )   
} 

// callback executer 
function callbackTester (callback,i) {     
    setTimeout( function(){callback(i);}, 20000 - i*1000); 
} 

// test function
for(var i=0 ; i<10 ; i++){
    callbackTester ( tryMe,i );
}

function waitFor(fnReady, fnCallback) {
    var check = function() {
        if (fnReady()) {
            fnCallback();
        }
        else {
            setTimeout(check, 100);  // wait another 100ms, and try again
        }
    };

    check();
}

http://jsfiddle.net/x061dx75/17/



回答3:

I personally would use promises for this, but you've said no promises (not sure why), so here's a generic sequencer algorithm in plain javascript (tested in the jsFiddle linked below):

function sequence(fn) {
    // initialize sequence data upon first use
    if (typeof sequence.low === "undefined") {
        sequence.low = sequence.high = 0;
        sequence.results = {};
    }
    // save id in local variable so we can reference it in the closure from the function below
    var id = sequence.high;

    // advance to next sequence number
    ++sequence.high;

    // initialize the result value for this sequence callback
    sequence.results[id] = {fn: fn, args: [], ready: false, context: null};

    return function(/* args */) {
        // save args and context and mark it ready
        var args = Array.prototype.slice.call(arguments, 0);
        // get the results object for this callback and save info in it
        var thisResult = sequence.results[id];
        thisResult.args = args;
        thisResult.context = this;
        thisResult.ready = true;

        // now process any requests in order that are ready
        for (var i = sequence.low; i < sequence.high; i++) {
            var result = sequence.results[i];
            // if this one is ready, process it
            if (result.ready) {
                // increment counter past this result
                ++sequence.low;
                // remove this stored result
                delete sequence.results[i];
                // process this result
                result.fn.apply(result.context, result.args);
            } else {
                // if this one not ready, then nothing to do yet
                break;
            }
        }
    };
}

// your usage:

google.maps.event.addListener(searchBox, 'bounds_changed', sequence(renderTerminalsOnMapAndFitBounds));
...
$.getJSON('getAllTerminals.json', sequence(renderTerminalsOnMapAndFitBounds));
.....
$.getJSON('getAllTerminalsInsideRectangle.json', sequence(renderTerminalsOnMapAndFitBounds));
...
$.getJSON('getAllTerminalsInsideCircle.json', sequence(renderTerminalsOnMapAndFitBounds));
...
$.getJSON('getBigTerminals.json', sequence(renderTerminalsOnMapAndFitBounds));
........

Working demo: http://jsfiddle.net/jfriend00/aqugm1fs/


Conceptually, what this does is as follows:

  1. Pass a substitute completion handler in place of the normal completion callback.
  2. This substitute function marks each response with a sequence id and saved the original completion handler.
  3. If a response comes back while another response with a lower sequence id is still pending, then the result is just stored and saved for later.
  4. As each response comes in, it processes as many responses in sequence as are ready

Note: while all the examples you have use the same callback function, this will work with any callback function so it would work with a mix of different types of operations.