Error: Meteor code must always run within a Fiber

“Error: Meteor code must always run within a Fiber. Try wrapping callbacks that you pass to non-Meteor libraries with Meteor.bindEnvironment.”

I’m sorry if this is a stupid question, but I am just getting back into Meteor and JS after taking over a year off and I was pretty new back then anyways. I’m a long time lurker, because usually I can find an answer for things through Google.

Unfortunately, this seems to be over my head. The more I read about Fibers, Threads, Promises, Asynchronous, Synchronous, aSync, bindEnviroment, callbacks, etc… the more confused I get. :sweat_smile:

I am trying to retrieve data from an API and store it in a collection for use in my app, then update that data every minute. Each API call returns about 300 to 400 objects, so I guess that may be why I am getting an error… that is a lot of data to manipulate in a minute. I also created a few arrays of data that is updated every five minutes, every hour, and every day for the purpose of adding some graphs to my app. I am not sure what part of this is not running in a Fiber, so I’m not sure where to even start.

Can anyone give me any idea what I’m doing wrong? Any hints as to how to make it more efficient would be much appreciated too!! I am trying to write a complicated app for a beginner, but I guess you have to learn somehow! :sweat:

This file is in my server folder and I’m certain the error is contained in it, as this file is all I’ve worked on all day:

//retrieve data from API every 60 seconds
setInterval(function () {

    //returns an array of objects
    var response = HTTP.get('GET', 'https://api.coinmarketcap.com/v1/ticker/');

    //iterate through each object
    for (var i = 0; i < (response.data.length); i++) {

        //assign each object's properties to a temp object, then import said object into MongoDB
        var coin = {};
        coin.id = response.data[i].id;
        coin.name = response.data[i].name;
        coin.symbol = response.data[i].symbol;
        coin.rank = response.data[i].rank;
        coin.priceUsd = response.data[i].price_usd;
        coin.priceBtc = response.data[i].price_btc;
        coin.volume24Hour = response.data[i]["24h_volume_usd"];
        coin.marketCap = response.data[i].market_cap_usd;
        coin.availableSupply = response.data[i].available_supply;
        coin.totalSupply = response.data[i].total_supply;
        coin.percentChange1Hour = response.data[i].percent_change_1h;
        coin.percentChange24Hour = response.data[i].percent_change_24h;
        coin.percentChange7Day = response.data[i].percent_change_7d;
        coin.lastUpdated = response.data[i].last_updated;
        coin.priceArray24H = [];
        coin.priceArray7D = [];
        coin.priceArrayAll = [];
        coin.volumeArray24H = [];
        coin.volumeArray7D = [];
        coin.volumeArrayAll = [];

        //See if the coin already has a document in the collection
        if (Cryptos.findOne({
                id: coin.id
            }) == null) {

            //Each number in the following arrays will be used as data points on a chart            
            //24H = 288 Data Points MAX (Updated every 5 minutes)
            //7D = 168 Data Points MAX (Updated every hour)
            //All = Unlimited Data Points (Updated every day)

            //Setup arrays for charts and enter the first value
            coin.priceArray24H.push({
                price: coin.priceUsd,
                timestamp: coin.lastUpdated
            });
            coin.priceArray7D.push({
                price: coin.priceUsd,
                timestamp: coin.lastUpdated
            });
            coin.priceArrayAll.push({
                price: coin.priceUsd,
                timestamp: coin.lastUpdated
            });
            coin.volumeArray24H.push({
                volume: coin.volume24Hour,
                timestamp: coin.lastUpdated
            });
            coin.volumeArray7D.push({
                volume: coin.volume24Hour,
                timestamp: coin.lastUpdated
            });
            coin.volumeArrayAll.push({
                volume: coin.volume24Hour,
                timestamp: coin.lastUpdated
            });

            //Insert new coin document into collection, because the coin is not already there
            Cryptos.insert(coin);

        } else {

            //Pull the arrays out of the collection to analyze or manipulate later
            coin.priceArray24H = Cryptos.findOne({
                id: coin.id
            }, {
                priceArray24H: true
            });
            coin.priceArray7D = Cryptos.findOne({
                id: coin.id
            }, {
                priceArray7D: true
            });
            coin.priceArrayAll = Cryptos.findOne({
                id: coin.id
            }, {
                priceArrayAll: true
            });
            coin.volumeArray24H = Cryptos.findOne({
                id: coin.id
            }, {
                volumeArray24H: true
            });
            coin.volumeArray7D = Cryptos.findOne({
                id: coin.id
            }, {
                volumeArray7D: true
            });
            coin.volumeArrayAll = Cryptos.findOne({
                id: coin.id
            }, {
                volumeArrayAll: true
            });

            //24H Array Logic - 288 Data Points MAX (Updated every 5 minutes)
            //If 5 minutes has gone by
            var length24H = coin.priceArray24H.length() - 1;
            if (coin.lastUpdated - (coin.priceArray24H[length24H].lastUpdated >= 300)) {
                //If there's less than 288 price points in the array
                if (coin.priceArray24H.length < 288) {
                    coin.priceArray24H.push({
                        price: coin.priceUsd,
                        timestamp: coin.lastUpdated
                    });
                    coin.volumeArray24H.push({
                        volume: coin.volume24Hour,
                        timestamp: coin.lastUpdated
                    });
                } else {
                    
                    //If array is at the maximum amount of data points wanted,
                    //Then remove the first value add the updated value to the end
                    coin.priceArray24H.shift();
                    coin.priceArray24H.push({
                        price: coin.priceUsd,
                        timestamp: coin.lastUpdated
                    });
                    coin.volumeArray24H.shift();
                    coin.volumeArray24H.push({
                        volume: coin.volume24Hour,
                        timestamp: coin.lastUpdated
                    });
                }
            }

            //7Day Array Logic - 168 Data Pints MAX (Every hour)
            //See if a hour has gone by since the last update
            var length7D = coin.priceArray7D.length() - 1;
            if (coin.lastUpdated - (coin.priceArray7D[length7D].lastUpdated >= 3600)) {

                if (coin.priceArray7D.length < 168) {

                    //If array has less than 168 data points
                    //Then add values to the end of the arrays
                    coin.priceArray7D.push({
                        price: coin.priceUsd,
                        timestamp: coin.lastUpdated
                    });
                    coin.volumeArray7D.push({
                        volume: coin.volume24Hour,
                        timestamp: coin.lastUpdated
                    });
                } else {

                    //If array is at the maximum amount of data points wanted,
                    //Then remove the first value add the updated value to the end
                    coin.priceArray7D.shift();
                    coin.priceArray7D.push({
                        price: coin.priceUsd,
                        timestamp: coin.lastUpdated
                    });
                    coin.volumeArray7D.shift();
                    coin.volumeArray7D.push({
                        volume: coin.volume24Hour,
                        timestamp: coin.lastUpdated
                    });
                }
            }

            //All Array Logic - Unlimited Data Points (Updated Every 24H)
            //See if a day has gone by since the last update
            if (coin.lastUpdated - (coin.priceArrayAll[coin.priceArrayAll.length() - 1].lastUpdated >= 86400)) {

                //Add to the end of the array if 24 hours have gone by since the last update
                coin.priceArrayAll.push({
                    price: coin.priceUsd,
                    timestamp: coin.lastUpdated
                });
                coin.volumeArrayAll.push({
                    volume: coin.volume24Hour,
                    timestamp: coin.lastUpdated
                });

            }

            //Update the coin's document since it is already in the collection             
            Cryptos.update({
                id: coin.id
            }, {
                $set: {

                    rank: coin.rank,
                    priceUsd: coin.priceUsd,
                    priceBtc: coin.priceBtc,
                    volume24Hour: coin.volume24Hour,
                    marketCap: coin.marketCap,
                    availableSupply: coin.availableSupply,
                    totalSupply: coin.totalSupply,
                    percentChange1Hour: coin.percentChange1Hour,
                    percentChange24Hour: coin.percentChange24Hour,
                    percentChange7Day: coin.percentChange7Day,
                    lastUpdated: coin.lastUpdated,
                    priceArray24H: coin.priceArray24H,
                    priceArray7D: coin.priceArray7D,
                    priceArrayAll: coin.priceArrayAll,
                    volumeArray24H: coin.volumeArray24H,
                    volumeArray7D: coin.volumeArray7D,
                    volumeArrayAll: coin.volumeArrayAll

                }
            });
        }
    }
}, 60 * 1000);

Hello, it’s probably because of the setInterval.

Read this : https://docs.meteor.com/api/timers.html

Hope it helps, have a great day!

You were right. Thank you so much!!! I was looking in all of the wrong places.

I guess I can put off dealing with Promises, et al for now. :slight_smile:

That stuff is super confusing to me!

No problem ! A basic rule of thumb is : If a function is going to be called asynchronously, it probably needs to be wrapped in Meteor.bindEnvironment.
What happens is that when setInterval calls the function you give it, this function does not have access to the current “context”, that’s why it complains.

Meteor.setInterval is exactly the same as native setInterval only it wraps the function you pass it in Meteor.bindEnvironment ! Meteor.bindEnvironment makes sure your function will have the right context at the time of execution, that’s all there is to it :slight_smile:

You can find a pretty cool explanation of all this in these videos : https://www.eventedmind.com/classes/meteor-fibers-and-dynamics-9ba17f98

Basically promises are a nice way to handle “callback hell”.
You can do without them in most cases, but I recommend that you learn to use them :slight_smile:
If you really don’t like them, you can achieve the same result by wrapping code inside Meteor.wrapAsync and surrounding it with a try/catch block.
But of course, you can only do that in meteor so it’s a bit better to be able to use promises when needed :slight_smile: