Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

JavaScript Mystery

I'm trying to extract a die roll from a chat window with the following sendChat('', rollStr, function(r) { // sendChat('massinit', 'roll result ="'+r[0].content+'"<br>'); var roll = JSON.parse(r[0].content); // sendChat('massinit', 'roll components ="'+Object.keys(roll)+'"<br>'); // initVal = String(roll.total); initVal = roll.total; }); // end sendChat with callback I have managed to confirm that the JSON-created 'roll' object does in fact possess 'total' as one of its top-level keys (via the second commented-out sendChat()), but initVal persists in remaining 'undefined' when I try to assign it the value from roll.total.  What am I missing here? Thanks in advance, Bill
1618521530

Edited 1618521541
The Aaron
Roll20 Production Team
API Scripter
What is the content of rollStr?
1618521937
The Aaron
Roll20 Production Team
API Scripter
BTW, using the log() function to dump things to the API Console Log will probably be a bit easier on you during development.
rollStr is just /r 1d20+a number, and I've previously dumped the entirety of the roll result (as a string, the first commented-out debug statement), and that shows the expected contents, including having a value for 'total'.  initVal is declared farther up in the parent function so it will still be in context after the chat message handler exits.  Does log() just accept a string as its argument?
1618532261
The Aaron
Roll20 Production Team
API Scripter
Log will take anything, it will do a JSON.Stringify() on anything that isn't a string before dumping it to the API Output Log.
1618541042
timmaugh
Forum Champion
API Scripter
This works for me... on('chat:message', (msg) => {     if (msg.type !== 'api' || !/^!tcb/.test(msg.content)) return;     let rollStr = `/r 1d20+2`;     sendChat('', rollStr, r => {         let o = JSON.parse(r[0].content);         log(`o is of type ${typeof o}`);         log(`Type and value of total is ${typeof o.total}, ${o.total}`);         let initVal = String(o.total);         log(`Type and value of initVal is ${typeof initVal}, ${initVal}`);     }); }); enter !tcb into chat and you should see this at the log (with different values, of course): "o is of type object" "Type and value of total is number, 14" "Type and value of initVal is string, 14"
Unfortunately, for some reason initVal loses its value outside the sendChat() call, in spite of the fact that it is scoped to the surrounding function.  I've also tried moving the next-level assignment inside the sendChat() call: sendChat('', rollStr, r => { // sendChat('massinit', 'roll result ="'+r[0].content+'"<br>'); let roll = JSON.parse(r[0].content); // sendChat('massinit', 'roll components ="'+Object.keys(roll)+'"<br>'); initVal = String(roll.total); sendChat('massinit', 'pr = "'+initVal+'"<br>'); myTurnOrder.push( { id: t, pr: initVal, custom: '' } ); // end myTurnOrder.push // initVal = roll.total; }); // end sendChat with callback log(myTurnOrder); What I'm trying to do is write a script that will roll initiative for all selected tokens and add them to the turn order.  myTurnOrder remains at zero length (to simplify my initial testing, I'm starting with a blank turn order) for all die rolls.  I see the die rolls as they are made, but no assignments are made to myTurnOrder (an earlier test confirmed that initVal became undefined outside the sendChat callback, before I had moved the assignment to myTurnOrder).  This is one of those things that really makes me miss C.  Assignments were assignments unless you're using a pointer, and you know when you're using a pointer because there's an asterisk.  How can a function-wide variable be volatile ?
1618550516
The Aaron
Roll20 Production Team
API Scripter
Oh, I see the confusion.  sendChat() when called that way is asynchronous. initVal, if outside the scope of the callback function, will not have a value assigned to it until after sendChat() finishes and calls the callback. Until that point it will be undefined or have whatever value it had.  Try this: log("first"); sendChat("","something",(msgs)=>log("second")); log("third"); You'll get in the log: "first" "third" "second" If you want to use the value from the result of sendChat(), you'll need to do it in the callback, or start looking into Promises. 
1618550681
The Aaron
Roll20 Production Team
API Scripter
This should help:&nbsp; <a href="https://wiki.roll20.net/API:Use_Guide#A_Treatise_on_Asynchronous_Functions" rel="nofollow">https://wiki.roll20.net/API:Use_Guide#A_Treatise_on_Asynchronous_Functions</a>
In my last attempt, I did move the assignment of initVal into an element of myTurnOrder inside the sendChat() call, but to no avail.&nbsp; Do I need to write myTurnOrder back into the Campaign() object inside the sendChat() call as well?&nbsp; Or is that doomed as well, since there seems to be no way to ensure that myTurnOrder will actually get appended to in between sendChat() calls. Doing a little research into Promises, I don't see how that helps since sendChat() doesn't seem to support them.
1618608295
timmaugh
Forum Champion
API Scripter
Bill (Do Not Kill) said: In my last attempt, I did move the assignment of initVal into an element of myTurnOrder inside the sendChat() call, but to no avail. That's because your log statement (after the sendChat() call) is actually running before the asynchronous stuff completes. If, in the course of your sendChat callback, you were to write something to a character sheet... you could put a log statement for that character sheet item right after you close the sendChat, and it won't report the right value... but when your code finishes running and you open the sheet to look at that item, it will be there. It's just a matter of timing. Like Aaron said, it either has to be in the callback, initiated by &nbsp;the callback, or utilize Promises.
1618612879
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
timmaugh said: Like Aaron said, it either has to be in the callback, initiated by &nbsp;the callback, or utilize Promises. Or my favorite bit, Async/await (which is just an extension of promises)
Thanks for the help, and I'm working on trying to incorporate it all into the callback function.&nbsp; I'm now trying to figure out how to decrypt the output object from the die rolls.&nbsp; I'll start a new thread once I run out of ideas there and have something specific to ask about.
1618613806
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
This is how I'd most likely do this using promises and async/await: const chatResolution = function(rollStr){ return new Promise((resolve,reject)=&gt;{ sendChat('', rollStr, function(r) { // sendChat('massinit', 'roll result ="'+r[0].content+'"&lt;br&gt;'); var roll = JSON.parse(r[0].content); // sendChat('massinit', 'roll components ="'+Object.keys(roll)+'"&lt;br&gt;'); // initVal = String(roll.total); resolve(roll.total); }); // end sendChat with callback }); }; const calcResult = async function(){ let rollStr = '1d20 + 5';//obviously you'd construct this somewhere, but keeping it simple for demo let initVal = await chatResolution(rollStr); log(`initVal:${initVal}`); };