2

I'm making several calls from the client to the server depending on a few things and I need them to wait until the one before it has finished and returned a value before the next one starts. Here's a small example of what I'm doing currently:

Client:

function doOrder() {
  var addDevices = Template.instance().addDevices.get();

  if (addLicense) {
    createCustomer();
  }

  if (addDevices) {
    var maintProrate = Session.get('maintProrate');
    var deviceCharge = (deviceFee * addDevices);
    doCharge(deviceCharge, 'DEVICES', 'deviceCharge', 'chargeMaintFee');
  }
}

function createCustomer() {
  var stripeArgs = Helpers.client.stripeArgs('form');

  Meteor.call('stripe', 'customers', 'create', stripeArgs, function(err, response){
    if (err) {
      console.log(err);
      Bert.alert(err, 'danger');
    } else {
      Session.set('customerInfo', response);
      Session.set('customerId', response.id);
    }
  });
}

function doCharge(amount, description, storeKey) {
  var chargeArgs = {
    amount: amount,
    currency: 'usd',
    customer: Session.get('customerId'),
    description: description
  };

  Meteor.call('stripe', 'charges', 'create', chargeArgs, function(err, response){
    if (err) {
      console.log(err);
      Bert.alert(err, 'danger');
    } else {
      Session.set(storeKey, response);
    }
  });
}

Server:

Meteor.methods({
  stripe(func1, func2, arg1) {
    check( func1, String );
    check( func2, String );
    check( arg1, Match.OneOf(Object, String));

    var func = Meteor.wrapAsync(Stripe[func1][func2], Stripe[func1]);
    return func(arg1, {});
  },
  stripe2(func1, func2, arg1, arg2) {
    check( func1, String );
    check( func2, String );
    check( arg1, Match.OneOf(Object, String));
    check( arg2, Match.OneOf(Object, String));

    var func = Meteor.wrapAsync(Stripe[func1][func2], Stripe[func1]);
    return func(arg1, arg2, {});
  }
});

So in the case that addLicense and addDevices are both set during the doOrder function, both createCustomer and doCharge will be ran. What I need is for the createCustomer function to finish and return a value before the doCharge function is ran.

I know that I could place the doCharge function in the callback of the createCustomer function but I have many more functions that need to be ran in order so I'd like to avoid callback hell.

I used Meteor.wrapAsync thinking that would do it but no dice. I still get return values from the API but they all happen at the same time.

Any ideas? Thank you in advance.

Kodie Grantham
  • 1,963
  • 2
  • 17
  • 27
  • It can be sequential without being synchronous. You don't want xhr calls to be synchronous. And you can avoid callback hell by simply being more functional, or using promises effectively. – Kevin B Jun 15 '16 at 15:21
  • @KevinB I guess I don't understand. I thought both of those meant the same thing. The `createCustomer` function returns a value that I need to pass in the `doCharge` function. However the `doCharge` function is being called at the same time as the `createCustomer` function thus returning an error from the API. – Kodie Grantham Jun 15 '16 at 15:27
  • a synchronous ajax request blocks the event loop until the request is complete, which would make the ajax request sequential, but it would also make your page appear to be broken/unresponsive until all of the requests were complete, which is bad. – Kevin B Jun 15 '16 at 15:28
  • ahh, that makes sense. So I need to make them sequential. But doesn't Javascript already work that way? I've seen other people with a similar problem (http://stackoverflow.com/questions/17460123/meteor-methods-returns-undefined) but their solution was a callback function which I do to store the session variables but the next function is ran before the session variable is set. – Kodie Grantham Jun 15 '16 at 15:33
  • Yes, you will need more callbacks to make this work sequentially without making it synchronous. The key though is to do so without introducing a pyramid of callbacks, which can be done with named functions and/or promises. – Kevin B Jun 15 '16 at 15:34
  • 1
    I could really advice promises for this kind of thing. Your code would basically just become similar to `createCustomer.then(doChange)` Meteor even seems to have a couple of promise libs to chose from. Roughly said, they're just a convenient way to structure multiple callbacks without the pyramid of doom. :) – Shilly Jun 15 '16 at 15:38
  • Would the next function still be in the global scope? I had my code working originally with a pyramid of callbacks but I ran into an issue where the functions were no longer in the global scope because of it and no longer had access to some variables that they usually do. – Kodie Grantham Jun 15 '16 at 15:44
  • 1
    Also, if I had more functions to be ran, would I just chain the promises? `createCustomer.then(doCharge).then(updateSubscription).then(finishOrder);`? – Kodie Grantham Jun 15 '16 at 16:02
  • Why not just call one method from the client and have it execute all the other methods? Why create a need for multiple round-trips? – Michel Floyd Jun 15 '16 at 16:33
  • @MichelFloyd The only downside I see to doing it that way is I would need to send a lot of variables over to the server to get it done. – Kodie Grantham Jun 15 '16 at 16:46
  • @Kodie bundle them up as an object. Or if they are documents from your collections then just get them on the server. Requiring multiple back-and-forths makes for a _chatty_ app which is _slow_, especially as network latency increases. – Michel Floyd Jun 15 '16 at 17:35
  • @MichelFloyd Thanks for the advice! I'll give that a shot! – Kodie Grantham Jun 15 '16 at 17:41
  • Thanks again @MichelFloyd! That worked great! – Kodie Grantham Jun 17 '16 at 16:47

1 Answers1

1

So as @MichelFloyd suggested I ended up moving all of my calls into a server-side method so that only one call from the client is required.

Kodie Grantham
  • 1,963
  • 2
  • 17
  • 27