Snkini
Snkini

Reputation: 641

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?

Upvotes: 2

Views: 2573

Answers (4)

klaucode
klaucode

Reputation: 330

I'm sorry, I don't like any of that solutions. What about convert Meteor.call callbacks to promises?

const meteorPromiseCall = (method: string, ...args: any[]) =>
  new Promise((resolve, reject) => {
    Meteor.call(method, ...args, (err: any, res: any) => {
      if (err) reject(err);
      else resolve(res);
    });
  });

And example of use:

const Dashboards = () => {

  const [data, setData] = useState(null);
  
  const readData = async () => {
    // Waiting to all Meteor.calls
    const res = await Promise.all([
      meteorPromiseCall(
        "reports.activitiesReport",
        DateTime.now().startOf("day").minus({ day: 30 }).toJSDate(),
        DateTime.now().startOf("day").toJSDate(),
      ),
      meteorPromiseCall(
        "reports.activitiesReport2",
        DateTime.now().startOf("day").minus({ day: 30 }).toJSDate(),
        DateTime.now().startOf("day").toJSDate(),
      ),
      meteorPromiseCall(
        "reports.activitiesReport3",
        DateTime.now().startOf("day").minus({ day: 30 }).toJSDate(),
        DateTime.now().startOf("day").toJSDate(),
      ),
    ]);
    setData(res[0]);
  };

  useEffect(() => {
    readData();
  }, []);

  if (!data) return <div>Loading...</div>;

  return (...)

Upvotes: 0

Nikhil Goswami
Nikhil Goswami

Reputation: 81

You must use promise for example future fibers

on server

Meteor.methods({
'methodCall': function(params...){
  var future = new Future();
  try{
    your code...
    future.return(result)
  catch(e){
    future.throw(e)
  }finally{
    return future.wait();
  }
 },
})

On client

Meteor.call('methodCall',params...,(err,res)=>{
  if(err){
   console.log(err);
  }else{
   console.log(res);
  }
});

link for ref https://github.com/jagi/meteor-astronomy/issues/562

Upvotes: 0

Jankapunkt
Jankapunkt

Reputation: 8413

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.

Upvotes: 2

gihef
gihef

Reputation: 220

One of the way is to reorganize slightly your code.

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");
  });
});

Upvotes: 1

Related Questions