Search code examples
javascriptnode.jsmeteorangular-meteor

How to wait for meteor call response and then execute other statements in javascript?


I have two meteor call in client events in meteor, where i want to execute one after another. But as i debugged the flow dosen't follow the way i want it to be.

client.js

Meteor.call('methodCall',param1,param2,param3,function (error, result) {
    if (error) 
        console.log(error.reason);

    Session.set("xyz",result);
});

var abc=Session.get("xyz");

Meteor.call('methodCall',abc,param2,param3,function (error, result) {
    if (error) 
        console.log(error.reason);

    console.log("result: "+result);
    Session.set("cdf",result);
}); 

var pqr=Session.get("cdf");

As you can see this is the code i want to run in sequential order i.e one after another. But when i debugged the code i found that the order of execution is:

1. Meteor will be called
3. session.get("xyz") return undefined.
4. Meteor will be called
6. session.get("cdf") return undefined.
2. session.set() will have results as value.
5. session.get() will not have any value.

The second meteor.call() will not execute successfully because the 1st parameter will not have any value as step 3 executed before step 2. So is there any way i can achieve this and wait for meteor call completion to execute next instructions?


Solution

  • I have made some research on the various options for such a situation as some others here might have faced it already, too.

    Option A- Nested calls in client

    The first and most obvious one is to do nested calls. This means to call the next function after the result has been received in the callback.

    // level 1
    Meteor.call('methodCall', param1, param2, param3, function (error, result) {
        // level 2
        if (error) console.log(error.reason);
    
        Session.set("xyz",result);
    
        Meteor.call('methodCall',result, param2, param3, function (error, result) {
            // level 3...
            if (error) console.log(error.reason);
    
            console.log("result: "+result);
            Session.set("cdf",result);
        }); 
    
    });
    

    Pros: classic js way, no fancy new concepts required, server methods sticks so a simple logic while client dies the complex work

    Cons: ugly, can cause confusion and sometimes hard to debug

    Requires: Template.autorun or Tracker.autorun to capture the changes from Session reactively.


    Option B - Wrap Async

    Many might have already found this method to be no.1 choice for structuring async code into sync code.

    Fibers (and wrapAsync utilizing fibers) make the code only look to be sync but the nature of execution remains async. This works the same way like Promises work or like async/await works.

    Pros: powerful when in a single environment

    Cons: not to be used with Meteor.call

    Requires: a fiber to run in

    Problem with Meteor.call

    However, you can't easily call a Meteor method using this feature. Consider the following code

    const param1 = "param1";
    const param2 = "param2";
    const param3 = "param3";
    
    
    const asyncCall = Meteor.wrapAsync(Meteor.call);
    const result1 = asyncCall("methodCall", param1, param2, param3);
    // result1 will be undefined
    

    To further explain I will cite the documentation:

    On the client, if you do not pass a callback and you are not inside a stub, call will return undefined, and you will have no way to get the return value of the method. That is because the client doesn’t have fibers, so there is not actually any way it can block on the remote execution of a method.

    Summary:Meteor.wrapAsync is not to be utilized together with Meteor.call.


    Option C - Bundle in one method

    Instead of trying to create a synced sequence of meteor calls, you could also provide all parameters and logic to a single server method, that returns an object which keeps all returned values:

    client.js

    const param1 = "param1";
    const param2 = "param2";
    const param3 = "param3";
    
    
    Meteor.call('methodCall', param1, param2, param3, function (err, result) {
      const xyz = result.xyz;
      const cdf = result.cdf;
    });
    

    server.js

    function _methodCall(p1, p2, p3) {
      // ... 
      return result;
    }
    
    Meteor.methods({
      'methodCall'(p1, p2, p3) {
        const result1 = _methodCall(p1, p2, p3);
        const result2 = _methodCall(result1, p2, p3);
        return {
          xyz: result1,
          cdf: result2,
        }
      }
    })
    

    This will create a sequential execution (by following the sequential logic you provided in your question) and returns all it's results in a bundled object.

    Pros: sequential as desired, one request - all results Cons: one extra method to be tested, can introduce tight coupeling between methods, return objects can become large and complex to parse for the clinet Requires: a good sense for method design

    If I find other options I will add them to this post.