Search code examples
meteorcallbacktimeoutfiber

Meteor [Error: Can't wait without a fiber] after a call to Email.send


I've created a very simple server using Meteor, to send an email after a timeout. When I use a timeout, the message is successfully sent but an error is thrown: [Error: Can't wait without a fiber].

Here's my code:

if (Meteor.isServer) {
  Meteor.startup(function () {
    // <DUMMY VALUES: PLEASE CHANGE>
    process.env.MAIL_URL = 'smtp://me%40example.com:[email protected]:25';
    var to = '[email protected]'
    var from = '[email protected]'
    // </DUMMY>
    // 
    var subject = 'Message'
    var message = "Hello Meteor"

    var eta_ms = 10000
    var timeout = setTimeout(sendMail, eta_ms);
    console.log(eta_ms)

    function sendMail() {
      console.log("Sending...")
      try {
        Email.send({
          to: to,
          from: from,
          subject: subject,
          text: message
        })
      } catch (error) {
        console.log("Email.send error:", error)
      }
    }
  })
}

I understand that I could use Meteor.wrapAsync to create a fiber. But wrapAsync expects there to be a callback to call, and Email.send doesn't use a callback.

What should I do to get rid of the error?


Solution

  • This happens because while your Meteor.startup function runs inside a Fiber (like almost all other Meteor callbacks), the setTimeout you use does not! Due to the nature of setTimeout it will run on the top scope, outside the fiber in which you defined and/or called the function.

    To solve, you could use something like Meteor.bindEnvironment:

    setTimeout(Meteor.bindEnvironment(sendMail), eta_ms);
    

    And then do so for every single call to setTimeout, which is a painfully hard fact.
    Good thing it's not actually true. Simply use Meteor.setTimeout instead of the native one:

    Meteor.setTimeout(sendMail, eta_ms);
    

    From the docs:

    These functions work just like their native JavaScript equivalents. If you call the native function, you'll get an error stating that Meteor code must always run within a Fiber, and advising to use Meteor.bindEnvironment

    Meteor timers just bindEnvironment then delay the call as you wanted.