7

I am attempting to send a text message when a user requests to reset their password. I would like to wait for the message to be sent to alert the user if it was successful or not. I am currently attempting to do it as follows:

async function sendResetPasswordTextMessage(req, res) {
    let result = {};

    let phoneNumber = req.body.phoneNumber;                

    if (phoneNumber === undefined) {                       
        return sendInvalidParametersMessage(res);          
    }                                                      

    phoneNumber = phoneNumber.toString();                  

    const userProfile = await models.UserProfile.findOne({ 
        where: {                                           
            phoneNumber: phoneNumber                       
        }                                                  
    });                                                    
    ************************** RELEVANT CODE TO ISSUE *************************
    if (userProfile) {
        const message = "Your username is:\n" + userProfile.username;
        const sent = await AWSSNSClient.sendMessage(message, phoneNumber);

        if (!sent) {
            result.error = setTitleAndMessage("Error", "An error occurred");
        } else {
            result.success = setTitleAndMessage("Success", "Message sent"); 
        }
    }
    return res.send(result);
    ***************************************************************************
}

In my other class AWSSNSClient, I have the following sendMessage function:

function sendMessage(message, phoneNumber) {
    const params = { 
        Message: message, 
        MessageStructure: "string", 
        PhoneNumber: "+1" + phoneNumber
    };
    let sent = false;
    sns.publish(params, function(err, data) {
        if (err) {
            console.log(err, err.stack); // an error occurred
        }
        else {
            sent = true;
        }
    });

    return sent;
}

I cannot figure out how to make sendMessage wait for sns.publish to return before it returns itself. I have tried making it an async method and adding await on sns.publish, but the function still returns before sent gets set to true.

I know that the messages are sending without error because I am receiving them and no console logs are printed.

havak5
  • 714
  • 1
  • 6
  • 20
  • 2
    As with other asynchronous things in Javascript, you must either provide a callback function to sendMessage which can can call on success or error, or return a promise that resolves/rejects on success or error. – Jim Cote Dec 14 '17 at 05:19

6 Answers6

22

Stumbled on this one via Google trying to figure this out myself today - short answer that I am now using:

You can now do this with Async/Await — and Call the AWS service (SNS for example) with a .promise() extension to tell aws-sdk to use the promise-ified version of that service function (SNS) instead of the call back based version.

The only caveat here is the containing function must ALSO be async to utilize the await syntax.

For example:

let snsResult = await sns.publish({
    Message: snsPayload,
    MessageStructure: 'json',
    TargetArn: endPointArn
}, async function (err, data) {
    if (err) {
        console.log("SNS Push Failed:");
        console.log(err.stack);
        return;
    }
    console.log('SNS push suceeded: ' + data);
    return data;
}).promise();

The important part is the .promise() on the end there. Full docs on using aws-sdk in an async / promise based manner can be found here: https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/using-promises.html

In order to run another aws-sdk task you would similarly add await and the .promise() extension to that function (assuming that is available).

For anyone who runs into this thread and is actually looking to simply push multiple aws-sdk promises to an array and wait for that WHOLE array to finish (without regard to which promise executes first) I ended up with something like this:

let snsPromises = [] // declare array to hold promises
let snsResult = await sns.publish({
    Message: snsPayload,
    MessageStructure: 'json',
    TargetArn: endPointArn
}, async function (err, data) {
    if (err) {
        console.log("Search Push Failed:");
        console.log(err.stack);
        return;
    }
    console.log('Search push suceeded: ' + data);
    return data;
}).promise();

snsPromises.push(snsResult)
await Promise.all(snsPromises)

Hope that helps someone that randomly stumbles on this via google like I did!

Necevil
  • 2,802
  • 5
  • 25
  • 42
  • This should be the accepted anser, we need to add .promise() at the end – alext Sep 14 '20 at 19:08
  • Once I collected the promises and used await Promise.all(snsPromises) I go t mine working. Thanks! – KingAndrew Mar 30 '21 at 21:41
  • 2
    I ran into an issue where the events are published twice while using `promise()` https://stackoverflow.com/questions/60711134/aws-lambda-sns-sending-topic-twice. It is also strange it still requires a callback when using `promise()` – TemporaryFix Mar 31 '21 at 20:09
10

stackdave will that actually wait?

Necevil "Search push suceeded will get logged twice" because you're mixing calling operations by passing a callback and using promises. You should only use one method of getting the result

let snsResult = await sns.publish({
    Message: snsPayload,
    MessageStructure: 'json',
    TargetArn: endPointArn}).promise()

will do the trick

Tim B0
  • 101
  • 1
  • 4
4

You can simply use callbacks for that. Modify your sendMessge like this

function sendMessage(message, phoneNumber, cb) {
    const params = { 
        Message: message, 
        MessageStructure: "string", 
        PhoneNumber: "+1" + phoneNumber
    };
    sns.publish(params, cb);
}

then on your main file you can supply callback like this

if (userProfile) {
  const message = "Your username is:\n" + userProfile.username;
  AWSSNSClient.sendMessage(message, phoneNumber, (err, data) => {
    if (err) {
      result.error = setTitleAndMessage("Error", "An error occurred");
    }
    else {
      result.success = setTitleAndMessage("Success", "Message sent");
    }
    res.send(result);
  });
}
Vipin Kumar
  • 6,441
  • 1
  • 19
  • 25
3

Here the right updated API, August 2018, Necevil answer send the sms twice.

// using config.env
AWS.config.region = 'eu-west-1';
AWS.config.update({
  accessKeyId: process.env.AMAZON_SMS_ID,
  secretAccessKey: process.env.AMAZON_SMS_TOKEN,
});

// parameters 
let params = {
   Message: contentSMS,  // here your sms
   PhoneNumber: mobile,  // here the cellphone
 };


 const snsResult = await sns.publish(params, async (err, data) => {
    if (err) {
       console.log("ERROR", err.stack);
    }
    console.log('SNS ok: ' , JSON.stringify (data));
  });
stackdave
  • 6,655
  • 9
  • 37
  • 56
  • 1
    This was also the issue I was facing (duplicate SNS publishes). If I left out the "async" before the (err, data) part, for some reason the callback gets called twice. I couldn't figure out why. But my Javascript may just not be up to date. This seems to work though. – blakeage Sep 24 '18 at 17:14
2

If you're having issues with duplicate SNS messages being sent, I fixed this issue by utilizing examples from AWS:

// Load the AWS SDK for Node.js
var AWS = require('aws-sdk');
// Set region
AWS.config.update({region: 'REGION'});

// Create publish parameters
var params = {
  Message: 'MESSAGE_TEXT', /* required */
  TopicArn: 'TOPIC_ARN'
};

// Create promise and SNS service object
var publishTextPromise = new AWS.SNS({apiVersion: '2010-03-31'}).publish(params).promise();

// Handle promise's fulfilled/rejected states
publishTextPromise.then(
  function(data) {
    console.log("Message ${params.Message} send sent to the topic ${params.TopicArn}");
    console.log("MessageID is " + data.MessageId);
  }).catch(
    function(err) {
    console.error(err, err.stack);
  });

By utilizing a traditional .then() I was able to squash the duplicate message bug mentioned in comments above.

Shawn
  • 513
  • 9
  • 18
  • This seems straightforward. I tried it and I don't get any consol.log from the data or err. I am in async: exports.handler = async(event, context) => Do I need to await? – KingAndrew Mar 30 '21 at 21:22
  • Ok. If I mix this one with @Necevil using the await Promise.all(snsPromises) I get all the console.logs that I was expecting. :-) – KingAndrew Mar 30 '21 at 21:39
0

You can create a async function what use the promise method

async function sendMessage(message, phoneNumber){
     const params = { 
        Message: message, 
        PhoneNumber: phoneNumber
    };
  
 return new Promise((resolve, reject) => {
    SNS.publish(params, (err, data) => {
      if (err) {
         console.log("Search Push Failed:");
        console.log(err.stack);
        return reject(err);
      } else {
            console.log('Search push suceeded:' + phoneNumber);
        return resolve(data);
      }
    })
    
    });
  
}

and then you can call

var  s=  await sendMessage(message,phoneNumber);