3

I have this problem:

The user clicks on a button A, then something gets created with an ajax call. The call returns some data.

Later, the user (enteres some text into an input and) will click on button B. This will start another ajax call. Now, this one needs the returned data from the first request.

This is no problem when the first call is fast enough. However, I have to assume that it could take a while, so when the user clicks on button B the first call may not be completed.

I don't want to disable button B as long as the first lasts. The user should click on button B even if the first ajax call has not yet completed. So it seems that I need to chain/queue the calls somehow.

How could I do this? And how could this be done with a longer sequence of calls. And is there a name for this scenario, is there already a pattern for it? Or am I just blind?

Rudimentary sample code:

$('button.a').click(function () {
    $.post('some/url/for/action/a', {})
        .done(function (returnedData) {

        })
});

$('button.b').click(function () {
    // now I need something from returnedData, which may not be available yet
    $.post('some/url/for/action/b', {something: returnedData.something})
        .done(function () {

        })
});

Note: the final aim is to speed up the process to improve UX.

(Feel free to improve the title of the question. I couln't express that better.)

robsch
  • 9,358
  • 9
  • 63
  • 104

4 Answers4

3

If you're OK working with the most recent versions of the ECMAScript spec, then you may want to look at using Promises. There's a seriously great explanation of Promises on Google's documentation for the web, but they're basically objects that we can use to wait for stuff to finish happening asynchronously.

If you wrap your first ajax call inside a Promise object, then you can come back to it later on (in your case, after the user clicks button B) and see if it's finished yet. If it has, then() you can do something in response!

var promiseA; // this variable will hold our Promise object

function postActionA() {
  return new Promise(function(resolve, reject) {
    $.post('some/url/for/action/a', {}).done(resolve); // this is the magic
    // when the ajax call completes, it calls the 'resolve' function
  });
}

$('button.a').click(function () {
  promiseA = postActionA();
  // by assigning our promise object to a variable
  // we can inspect its state later on
  promiseA.then(function(returnedData) {
    // do stuff with returnedData here if you like
  });
});

$('button.b').click(function () {
  // assuming that promiseA was assigned earlier
  // we can wait for it to resolve, then make our next ajax call
  promiseA.then(function (returnedData) {
    $.post('some/url/for/action/b', {something: returnedData.something})
      .done(function () {
      // do something after the second ajax call completes
      });
  });
});

If you're not able to use the latest version of ECMAScript and would rather use jQuery, we can make use of jQuery's deferred objects to do something similar.

var deferredA; // this variable will hold our Deferred object

$('button.a').click(function () {
  deferredA = $.post('some/url/for/action/a', {});
  // by assigning our deferred object to a variable
  // we can inspect its state later on
  deferredA.done(function(returnedData) {
    // do stuff with returnedData here if you like
  });
});

$('button.b').click(function () {
  // assuming that deferredA was assigned earlier
  // we can wait for it to resolve, then make our next ajax call
  deferredA.done(function (returnedData) {
    $.post('some/url/for/action/b', {something: returnedData.something})
      .done(function () {
      // do something after the second ajax call completes
      });
  });
});
Graham Harper
  • 487
  • 5
  • 11
  • Thanks! Sounds reasonably. Are there any problems with IE? – robsch Jan 25 '17 at 13:57
  • @robsch - unfortunately, yes. Promises work natively on all browsers except for IE - although they are fully supported on the new Edge browser. There are some 3rd party JS modules that you can use to polyfill the same kind of behaviour, for example https://www.promisejs.org/ – Graham Harper Jan 25 '17 at 14:00
  • I suppose it could be done with [`$.then()`](https://api.jquery.com/jQuery.when/) somehow... – robsch Jan 25 '17 at 14:02
  • Yes! I've never used https://api.jquery.com/jQuery.when/, but it looks very similar and I think I can see how the API works. I'll edit my answer to include an alternative implementation. – Graham Harper Jan 25 '17 at 14:10
  • @robsch I didn't realise that the jQuery post() method already returns a deferred object. These are very similar to promises, so I've provided a simplified example of how you might use them in your case. – Graham Harper Jan 25 '17 at 14:16
  • I guess `returnedData` would have to be set to another variable outside the functions. Or can `returnedData` be passed to the next doneHandler? – robsch Jan 25 '17 at 14:49
  • `returnedData` can be passed through doneHandlers, I think – Graham Harper Jan 25 '17 at 17:10
1

My idea is based on queueing the ajax call on button click. Then the call is picked up from the queue and fired.

This way you can click random sequences (like A,B,A,A,B,A,B,B) and every subsequent call will wait for previous ones.

Passing data from one call to another is done via global variable.

The code:

var queue = [];
var processing = false;
var dataFromLastCall = {}; //data from previous call

function register(callParams){
  queue.push(callParams);
  if(processing == false){
    process(queue);
  }
}

function process(){
  processing = true;
  var call = queue.shift();

  $.when(jQuery.ajax(call)).then(function(){
    if(queue.length > 0){
      process(); //get another
    }else{
      processing = false; //all done
    }
  });
}

$("button.a").click(function(e){
  e.preventDefault();
  register({
    type: 'POST',
    url: 'some/url/for/action/a',
    data: dataFromLastCall,
    success: function(newData){
      dataFromLastCall = newData;
    }
  });
});

$("button.b").click(function(e){
  e.preventDefault();
  register({
    type: 'POST',
    url: 'some/url/for/action/b',
    data: dataFromLastCall,
    success: function(newData){
      dataFromLastCall = newData;
    }
  });
});

Note, that this sample does not handle error scenarios.

adosan
  • 166
  • 3
  • 9
0
var isFirstCallFinished = false;


$('button.a').click(function () {
$.post('some/url/for/action/a', {})
    .done(function (returnedData) {
         isFirstCalFinished = true;
    })
});

$('button.b').click(function () {
// now I need something from returnedData, which may not be available yet
setInterval(function () {
 if(isFirstCallFinished)  {
 // Cancel Interval
 // Call postMe()
 }
}, 1000);
});
function postMe() {
 $.post('some/url/for/action/b', {something: returnedData.something})
    .done(function () {

    })
  });
 }
Shubhranshu
  • 511
  • 3
  • 12
  • This will work, but why not get rid of the unecessary interval delay by having two variables (`isFirstCallFinished` and `hassButtonBeenPressed`) and then test `if(isFirstCallFinished && hasButtonBeenPressed)` in both the Ajax callback and the click listener? – apsillers Jan 25 '17 at 13:05
  • That may work but it is not a proper solution. I think using means of jQuery are much better. – robsch Jan 25 '17 at 14:15
-2

You actually want to make a synchronous call. I suggest you to use $.ajax instead of $.post since it's more customizable.

Your button B function would be something like this:

$.ajax({
  type: 'POST',
  url: 'some/url/for/action/b',
  data: data,
  success: success,
  async:false
});

The async:false option makes your request synchronous.

Lucas Borges
  • 140
  • 2
  • 13
  • What if the data that comes on the success of first call is not available when the user clicks on the Button B?? – Shubhranshu Jan 25 '17 at 12:57
  • @Shubhranshu In this case, the user *can't* click on button B (or anything else) before the Ajax call completes, because the browser will be totally frozen during the entire time the synchronous Ajax call is completing. This is solution by make the entire page temporarily unresponsive, which is why synchronous Ajax is discouraged. – apsillers Jan 25 '17 at 13:09
  • 1
    @Lucas, I think you should just read this requirement once again. **I don't want to disable button B as long as the first lasts. The user should click on button B even if the first ajax call has not yet completed.** – Shubhranshu Jan 25 '17 at 13:10
  • @Shubhranshu with regards to the stuff in bold. but if B relies on A Stuff... then how would it make sense? that you can "The user should click on button B even if the first ajax call has not yet completed" – Seabizkit Jan 25 '17 at 13:38
  • @Seabizkit, the *result* of clicking on button B may rely on having data from clicking on button A, but without knowing more about the spec for how the page should work, we cannot assume that we should make the whole page unresponsive, or prevent the user from clicking button B... assuming that button A has been clicked already! – Graham Harper Jan 25 '17 at 13:43
  • @GrahamHarper so the OP should state the requirement for this, as i would be interested in the answer. Maybe it should be Asked then..? OP? – Seabizkit Jan 25 '17 at 13:47
  • @Seabizkit Of course, when I say the user should be able to click on button b, I don't assume that the user could have been died by action a and cannot click at all :-) Clicking on button b means the action should be done, optionally it gets delayed. Unresponsiveness is not an option (actually never). – robsch Jan 25 '17 at 14:10
  • I can't think of a valid use case for a synchronous AJAX call - they can make the browser unresponsive. Proper handling of async requests via promises would usually be considered best practice. – Brian Jan 25 '17 at 14:53
  • @robsch i never said it should be Unresponsiveness i.e. synchronous or anything like that... i was just under the impression that it could be hidden or disabled until A is complete. I was merely trying to understand the logic around being able to execute B when B relies on A completing. – Seabizkit Jan 26 '17 at 07:09
  • @Seabizkit Thanks for your efforts in any case. No, that was the thing that makes it difficult: action B should be possible even if action A is not fully completed (but action A must be done before action B). In my case action A starts the creation of an object at the server that will be used of action B later. But the creation takes some time and nevertheless the user should be able to prepare some operations on this object that should be applied directly after creation. Otherwise it would a simple programming challenge ;-) – robsch Jan 26 '17 at 07:24
  • @robsch can you not combined them? i.e whatever is in B, add to A, use threading... to split the process so that they are down simultaneity and then rejoin when needed. – Seabizkit Jan 26 '17 at 07:53
  • @Seabizkit I don't get what you mean. Do you mean that I should wait with the first action until the second gets executed? This would not be an option since the whole issue is about speed up things (I will insert that into my question). Amendment: think of that the action A will return an id which is required by action B. – robsch Jan 26 '17 at 09:08
  • @robsch do threading.... so both happen at the same time off one request. 2 thread... or more if you can split it up more... look into threading. – Seabizkit Jan 26 '17 at 09:16
  • @Seabizkit Then I would need to defer the first action to the point where action B takes place. Assuming that action A takes some time the user has to wait a longer to complete action B. But action B should be executed fast. Note: JS has no real threading AFAIK. – robsch Jan 26 '17 at 09:22
  • is all of this in JS? if not and your hitting the back-end.... I'm suggesting it be done there.... JS is single threaded by nature. your code suggests it hitting a back-end..... – Seabizkit Jan 26 '17 at 09:24