开发者

Coordinating Asynchronous Requests in Javascript

开发者 https://www.devze.com 2023-01-06 11:47 出处:网络
I want to get two resour开发者_运维技巧ces using two asynch calls. I want to proceed only when both resources have been retrieved.

I want to get two resour开发者_运维技巧ces using two asynch calls. I want to proceed only when both resources have been retrieved.

How can I do this elegantly in JS?

This would work:

getStuff1(function (result1) {
    getStuff2 (function (result2) {
        // do stuff with result1 and result2
        ....
    }
}

but stuff2 only starts after stuff1 completes. I'd prefer to start stuff2 while waiting on stuff1.


If you know that functions are in fact first-class objects in Javascript, you can come up with a fairly elegant solution.

Without any extra objects, or global variables.

function callback1() {
  callback1.done = true;
  commonCallback();
}

function callback2() {
  callback2.done = true;
  commonCallback();
}

function commonCallback() {
  if (callback1.done && callback2.done) {
    // do stuff, since you know both calls have come back.
  }
}

Why is this so elegant? Because you've encapsulated the data, your scope is free from useless variables and the code is more readable than ever. How cool is that? :)


UPDATE

And if you want a bit more general solution you may try the following:

function callback() {
  callback.number -= 1;
  if (callback.number === 0) {
    // do stuff since all calls finished
    callback.last();
  }
}
callback.newQueue = function(num, last) {
  callback.number = num;
  callback.last   = last;
}

// EXAMPLE USAGE

// our last function to be invoked
function afterEverythingDone(){ alert("done"); }

// create a new callback queue
callback.newQueue(3, afterEverythingDone);

// as time passes you call the callback
// function after every request
callback();
callback();
callback();

// after all call is finished
// afterEverythingDone() executes

Awesomeness again :)


One way is to use the same callback for both requests and proceed when both are complete:

var requestStatus = {
  fooComplete: false,
  barComplete: false
};

function callback(data) {
  if (isFoo(data)) {
    requestStatus.fooComplete = true;
  } else if (isBar(data)) {
    requestStatus.barComplete = true;
  }

  if (requestStatus.fooComplete && requestStatus.barComplete) {
    proceed();
  }
}

getAsync("foo", callback);
getAsync("bar", callback);

You'll probably want to flesh this out into a class.

Edit: added the async calls for clarity


You could have the callback function for each one indicate that their respective request has come back, and then execute the same common function. To illustrate:

var call1isBack = false;
var call2isBack = false;

function call1Callback() {
  call1isBack = true;
  commonCallback();
}

function call2Callback() {
  call2isBack = true;
  commonCallback();
}

function commonCallback() {
  if (call1isBack && call2isBack) {
    // do stuff, since you know both calls have come back.
  }
}


Use a common callback handler with a counter that only allows passage into the "actual" processing section after the counter meets or exceeds the number of pending requests:

var commonHandler = (function() {
  var counter=0, pendingCalls=2;
  return function() {
    if (++counter >= pendingCalls) {
      // Do the actual thing...
    }
  }
})();

makeAjaxCall({args:args1, onComplete:commonHandler});
makeAjaxCall({args:args2, onComplete:commonHandler});

Using a closure around the anonymous function lets you avoid using a global variable for the counter.


Here's a snippet from a concurrent library I'm working on. All you need to do is instantiate a new Concurrent.Counter with the number of requests to await (before you execute them), and the callback to execute when they have finished. Before each of the asynchronous functions returns, have it call the decrement() method of the counter; once the counter has been decremented the number of times specified, the callback will be executed:

// Ensure the existence of the "Concurrent" namespace
var Concurrent = Concurrent || {};

/**
 * Constructs a new Concurrent.Counter which executes a callback once a given number of threads have
 * returned. Each Concurrent.Counter instance is designed to be used only once, and then disposed of,
 * so a new one should be instantiated each additional time one is needed.
 *
 * @param {function} callback The callback to execute once all the threads have returned
 * @param {number} count The number of threads to await termination before executing the callback
 */
Concurrent.Counter = function(callback, count) {

    /**
     * Decrements the thread count, and executes the callback once the count reaches zero.
     */
    this.decrement = function() {
        if (!(-- count)) {
            callback();
        }
    };
};

// The number of asynchronous requests to execute
var requests = 10,

// Executes a callback once all the request tasks have returned
counter = new Concurrent.Counter(function() {
    // this will be executed once the tasks have completed
}, requests),

// Tracks the number of requests made
i;

for (i = 0; i < requests; i ++) {
    setTimeout(function() {
        /*
         * Perform an asynchronous task
         */

        // Decrement the counter
        counter.decrement();
    }, 0);
}


This is written off the top of my head, but it should work.

function createCoordinator(onFinish) {
    var count = 0;
    return function (callback) {
        count++;
        return function () {
            if (callback.apply(this, arguments))
                count--;
            if (count == 0)
                onFinish();
        }
    }
}

var coordinate = createCoordinator(function () { alert('done!') });

// Assume sendAJAX = function (url, onreadystatechange)
sendAJAX('url1', coordinate(function () {
    if (this.readyState != 4) 
        return false; // Return false if not done
    alert('Done with url1!');
    return true;
}));
sendAJAX('url2', coordinate(function () {
    if (this.readyState != 4) 
        return false; // Return false if not done
    alert('Done with url2!');
    return true;
}));
0

精彩评论

暂无评论...
验证码 换一张
取 消