Returning value from callback within Meteor.method

2019-01-14 13:57发布

I am running into something I don't understand with Meteor. I have this method, which takes a query, sends it to amazon, and then in the callback of that function I try to return the results.

Meteor.methods({
    'search': function(query) {
        var bookInfo;
        if (Meteor.isServer) {
            amazon.execute('ItemSearch', {
                'SearchIndex': 'Books',
                'Keywords': query,
                'ResponseGroup': 'ItemAttributes'
            }, function(results) {
                bookInfo = results;
                console.log(bookInfo);
                return bookInfo;
            });
        }
    }
});

But when I put the following into the console in my browser (chrome):

Meteor.call('search', 'harry potter', function(error, response) {
    console.log('response:', response);
});

I get the this:

undefined
response: undefined          VM13464:3

I think I understand that the first undefined comes from the method not returning anything on the client, but the callback doesn't seem to work at all.

The amazon.execute(...) is definitely returning something, as the console.log right above the return does log the info I'm looking for.

Any ideas what's going wrong and how I can fix it?

4条回答
不美不萌又怎样
2楼-- · 2019-01-14 14:36

You need to use Future to achieve your goal.

How to use future since Meteor 0.6?

Meteor.startup(function () {
 Future = Npm.require('fibers/future');

 // use Future here
}

Your method rewritten with Future:

Meteor.methods({
 'search': function(query) {

    var future = new Future();

    amazon.execute('ItemSearch', {
            'SearchIndex': 'Books',
            'Keywords': query,
            'ResponseGroup': 'ItemAttributes'
    }, function(results) {
       console.log(results);

       future["return"](results)

    });

    return future.wait();
 }
});

Now it should work.

Meteor.call('search', 'harry potter', function(error, response) {
   if(error){
    console.log('ERROR :', error);
   }else{
    console.log('response:', response);
   }

});

If you want to learn more about Future library I recommend watching screencast


Update on 26/12/2017

I just wanted to update this answer as you can achieve the same thing using promise and so, get rid of the "fibers" depedencies :)

An example is worth a thousand words

import scrap from 'scrap';

Meteor.methods({
    'hof.add'(el) {
        check(el, {
            _link: String
        });

        const promise = getHofInfo(el._link)
            .then((inserter) => {
                inserter.owner = Meteor.userId();
                Hof.insert(inserter);
                return true;
            })
            .catch((e) => {
                throw new Meteor.Error('500', e.message);
            });
        return promise.await();
    }
});


function getHofInfo(_link) {
    return new Promise((resolve, reject) => {
        scrap(_link, function (err, $) {
            if (err) {
                reject(err);
            } else {
                const attakers = $('#report-attackers').find('li').text();
                const defender = $('#report-defenders').find('li').text();
                const _name = attakers + ' vs ' + defender;
                const _date = new Date();
                resolve({ _name, _date, _link });
            }
        });
    });
}
查看更多
祖国的老花朵
3楼-- · 2019-01-14 14:45

For anyone new to Meteor seeing this question and wondering why a library like Future or Fiber is necessary, it's because that call to amazon.execute is asynchronous.

In Javascript, many operations that take an extended period of time don't run one line after the next; Examples like writing to a database, using window.setTimeout, or making HTTP requests. With methods like these, historically you've needed to wrap the code you want to run after the fact in a callback.

Future and Fibers provide syntactic sugar and additional functionality, but their core functionality is the same.

Meteor uses special behind-the-scenes tricks to make certain built-in operations (like accessing MongoDB) appear synchronous, while still taking advantage of the increased performance of asynchronous code. For this reason, you normally only have to worry about async when using external packages (like the Amazon one in this example).


Here's a fully fleshed-out example of using both Future and Fibers:

There are some great articles explaining the nature of Sync/Async in Meteor on the Discover Meteor blog and at the Meteor Chef

查看更多
叼着烟拽天下
4楼-- · 2019-01-14 14:46

Meteor methods are asynchronous, you can get the result by many way.

Using npm module fibers (The other answer are explaining it very clearly).

There are some other way w/o using npm module :

Via Session variable :

    Meteor.call('myMethod',args, function(error, result) { 
  if (error) { Session.set('result', error) } // Note that the error is returned synchronously 
  else { 
    Session.set('result', result) // Using : Session.get('result') will return you the result of the meteor call !
  }
});

Or Via template variable :

    Template.hello.onCreated(function helloOnCreated() {
  // counter starts at 0
  this.message = new ReactiveVar(0);
});

Template.hello.helpers({
  message() {
    return Template.instance().message.get();
  },
});

Template.hello.events({
  'click button'(event, instance) {
    Meteor.call('myMethod', args, function (error, result) {
      if (error) { Template.instance().message.set(error); }
      else {
        Template.instance().message.set(result);
      }
    })
  },
});

Hope it will help !

查看更多
地球回转人心会变
5楼-- · 2019-01-14 15:02

one better solution

using Fiber package

var Fiber = Npm.require('fibers');
...
Meteor.methods({
    callAsync: function (args) {
        var fiber = Fiber.current;

        async(function (args) {
            ...
            fiber.run(res);
        });

        return Fiber.yield();
    }
});
查看更多
登录 后发表回答