Throw new Meteor.Error in Callbacks

Hello .
I am using a package to send sms in meteor methods.
And I want an error to be sent to the client if the SMS is not successful.
So I used the following code…

api.VerifyLookup({
    receptor: ToRegister.phone_number,
    token: auth_code,
    template: "register"
}, function(response, status)  {
    if ( status !== 200) {
        throw new Meteor.Error(400,'somthing ','somthing');
    }
});

In the callback of this method, you will see that I checked the status.
But the software encounters an error and gives an error on the server side.

A good example in this SO answer: https://stackoverflow.com/questions/43060284/error-handle-in-meteor-in-calling-server-side-method

Basically in your client method call, read the error and display it to your user via any frontend alert system you may be using.

1 Like

It’s a bit different to that SO answer since you’re using an async method

The problem is that the method has returned before your callback runs and so the error doesn’t get thrown in the context of a method and doesn’t get sent to the client.

The general flow of your function currently is:

** Method comes in
>> Send aync function request to send sms
** return empty response to client -> client sees no error
<< async function finishes and runs callback, here error is set, so you try to send to client by throwing, but empty response already sent to client.

What you want to do is wait for the callback to finish first.

There’s three ways you can do this with Meteor

1. Use Meteor.wrapAsync

// Wrap the function you want to call
// The second parameter sets `this` in the wrapped function. 
// I have no idea if the GoogleApi module you're using needs this, but I'm including it just in case
const wrappedApi = Meteor.wrapAsync(api.VerifyLookup, api);
// (...)
    'sendSMS'() {
        api.VerifyLookup(, function(response, status)  {
            });
        try {
            const status = wrappedApi({
                receptor: ToRegister.phone_number,
                token: auth_code,
                template: "register"
            });
            if ( status !== 200) {
                throw new Meteor.Error(400,'somthing ','somthing');
            }
        } catch (error) {
            throw new Meteor.Error(400,'somthing ', error);
        }
    }

Here’s the docs on Meteor.wrapAsync:

Long before Promises and async functions, Meteor provided sync style async calls on the server using Fibers. If you’re curious, you can get a rundown here: https://benjamn.github.io/goto2015-talk/#/

2. Return a Promise:

'fetchMessages'() {
    // Return the promise, that will *eventually* resolve with the value you want to send
    return new Promise(function (resolve, reject) { 
        api.VerifyLookup({
            receptor: ToRegister.phone_number,
            token: auth_code,
            template: "register"
        }, function(response, status)  {
            if ( status !== 200) {
                reject(new Meteor.Error(400,'somthing ','somthing'));
            }
            resolve(status);
        });
    });
}

This works because Meteor checks if you’re returning a promise from a method and will automatically await the result before sending it to the client

3. Use Async/Await

async functions and async/await work best in when the library you’re using already returns promises or if you can promisify the function in question.
I’ll use the pify module to promisify the function in the example

import pify from 'pify'
// promisify the function you want to call
const wrappedApi = pify(api.VerifyLookup);

(...)
// Change the function declaration to an async function
'sendSMS': async function() {
    // Now when we run the promisified function it returns a promise that we 
    // can wait for the value of with `await`
    try {
        const status = await wrappedApi({
            receptor: ToRegister.phone_number,
            token: auth_code,
            template: "register"
        });
        if ( status !== 200) {
            throw new Meteor.Error(400,'somthing ','somthing');
        }
        return status;
    } catch (error) {
        throw new Meteor.Error(400,'somthing ', error);
    }
}

Note that await is only available inside an async function. async functions always return a promise.

This one is most similar to the Meteor example, except that it uses pure javascript.
One notable difference is that when Meteor sees an async function it acts as though you ran this.unblock() inside the function, and so the order in which methods are called is not guaranteed (unlike with wrapAsync).

4 Likes

Thanks, that’s a great writeup :slight_smile:

Yeah, callback issues are so common that I have a saved snippet and just edit the code blocks to be relevant :laughing:

1 Like