Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Returning value from callback within Meteor.method

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?

like image 276
fnsjdnfksjdb Avatar asked Nov 20 '13 06:11

fnsjdnfksjdb


2 Answers

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 });
            }
        });
    });
}
like image 125
Kuba Wyrobek Avatar answered Oct 06 '22 00:10

Kuba Wyrobek


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

like image 36
Joshua Comeau Avatar answered Oct 05 '22 23:10

Joshua Comeau