Trying to write a function to use callbacks to sen

2019-08-18 05:33发布

I'm trying to write a function to use callbacks to send a message in Facebook messenger. I need to do this because I'm having problems sending text from an array. The messages are sent, but not in the correct order. I THINK this is because Nodejs is looping over the elements faster than it can send the text. See my question about this here.

So now I am trying to rewrite my send functions using callbacks, in the vain hope that I can somehow FORCE NodeJS to actually WAIT before jumping to the next element!

So far I have the following code:

Main send function:

sendWithCallback: function(messageData, callback) {
    request({
            uri: 'https://graph.facebook.com/v2.6/me/messages',
            qs: {
                access_token: config.FB_PAGE_TOKEN
            },
            method: 'POST',
            json: messageData

        }, function (error, response, body) {
            if (!error && response.statusCode === 200) {
                let recipientId = body.recipient_id;
                console.log("Message sent to recipient '%s'", recipientId);
                callback(true);
            } else {
                console.error("Could not send message: ", response.statusCode, response.statusMessage, body.error)
                callback(false);
            }
        }
    );
},

Function for sending a "multi part" message (i.e. an Array of text):

sendMultipartMessage: function(recipientId, textArray) {
    let messageData, msgPart, msgLength, count = 0;
    msgLength = textArray.length;
    while (count < msgLength) {
        msgPart = textArray[count];
        messageData = {
            recipient: {
                id: recipientId
            },
            message: {
                text: msgPart
            }
        };
    }
    self.sendWithCallback(messageData, function(sent) {
        if (sent) {
            count++;
            console.log("Message part %s sent.", msgPart);
        }
        else {
            console.log("Couldn't send message");
        }
    });
},

In my head, this code works properly! It sends the text (taken from the array), then increments the count until it is equal to messageLength. But in reality it DOESN'T do that. Instead, it just goes into an infinite loop (which I can't see happening in my logs) and then crashes the app.

WHAT am I doing wrong?

1条回答
三岁会撩人
2楼-- · 2019-08-18 06:22

If we simplify your loop it essentially becomes this:

let count = 0;
while (count < msgLength) {
  messageData = data;
}

You never increment count.

I think that you intend to move the self.sendWithCallback call inside of the while loop. However, this still won't do what you want and will run forever. Even if it did do what you wanted, it wouldn't solve the problem of sending messages out in order.

JavaScript's concurrency model uses an event loop with "run-to-completion." You can pass messages to the event queue using something like request which you call by sendWithCallback. This only adds a message to the queue, but that message is not processed until the current running block completes. That means that your while loop actually has to complete before any of your requests start running. We can construct a simpler example with setTimeout:

let count = 0;
while (count < 1) {
  setTimeout(() => {
    count++;
  }, 1000);
}
console.log('while loop completed');

In the above the while loop never completes because count never gets incremented in the same block (console.log will never be called). It needs to complete before it can start processing the asynchronous messages you are creating with setTimeout.


You could actually just rewrite it like this:

textArray.forEach(msgPart => self.sendWithCallback(msgPart, sent => {
  if (!sent) console.error('Could not send message');
});

However this doesn't guarantee the order that the messages were sent and it will send messages even if one of the messages triggers an error. If you want to send them in order you will have to recursively call sendWithCallback within the callback for the next message once the previous one completes. That might look something like this:

let count = 0;
const sendMessage = (textArray, count) => {
  self.sendMessageWithCallback(textArray[count], sent => {
    count++;
    if (sent && count < textArray.length) {
      sendMessages(textArray, count);
    }
  });
}
sendMessages(textArray, 0);

If you were using promises and async/await you could write this much more simply as something like:

for (count = 0; count < msgLength; count++) {
  await self.sendMessageAsync(textArray[count]);
}

However this would require a larger rewrite of the surrounding code and using something like request-promise instead of just request.

查看更多
登录 后发表回答