JavaScript Promises

Workarounds and usability notes.

JavaScript Promises

Postby John Robin Dove » Tue Mar 14, 2023 9:02 am

Hi Clifton,
Did you ever get anywhere with promises? I've been reading all kinds of explanations and tutorials for days and days now but most of them are as clear as mud to me! However, I came across this page https://www.w3docs.com/snippets/javascript/what-is-javascript-version-of-sleep.html and have adapted it to my needs and it seems that, without understanding all the complexities of promises one can still use them. ;) My problem was with trying to emulate the TB request or VS MsgBox systems. The message box was fine but one key element, the suspension of execution, was missing. I got round this with a horribly clunky system which uses a global variable nextAction and generates huge lists of if ... else if... else if, if nextAction = 1 do this, if nextAction = 2 do that etc. It works but creates an excessive quanity of code, to say the least.

Here is how I have used the 'sleep function'. https://www.mediacours.com/tb_examples/newrequest/ Please have a look at it and tell me if you think it's safe. It seems to work perfectly and effectively suspends execution for as long as is necessary. I have put the TB 9.01 file etc here https://www.mediacours.com/tb_examples/newRequest.zip in case you or anyone else might like to use it. If you can't find anything wrong with the system, I shall apply it to my program which will probably take weeks but will make it much easier to understand and maintain.

John
John Robin Dove
 
Posts: 486
Joined: Thu Jan 23, 2014 4:35 am

Re: Promises

Postby Clifton » Tue Mar 14, 2023 4:04 pm

This is pretty cool. I will definitely watch your development on the use of Promises.
Clifton
Site Admin
 
Posts: 732
Joined: Tue Jan 14, 2014 1:04 am

Re: JavaScript Promises

Postby John Robin Dove » Tue Mar 21, 2023 7:12 am

Hi Clifton,
After a promising start (no pun intended) ;) I became a bit discouraged. I started looking into asynchronous programming after using it sucessfully in VB.NET. In VB.NET the process is incredibly simple. You just add the prefix async to functions and subs and on the line where the program has to wait you put await and the whole program stops until the desired return value arrives. It can be a bit tricky getting asynchronous functions to return variables in the correct format but other than that it's pretty straightforward. I used it to send messages to and from a cef browser object which has become vital because Microsoft has not provided a useful alternative to its webbrowser object which is still based on Internet explorer.

In Javascript, things seem far less easy to me. You can't just use await to stop execution anywhere in the program. You can only suspend execution within a single block in a function. After a lot of trial and error, I have created this compromise solution. It uses a lot of code but it will carry out a linear list of tasks. This was not the case with my previous, crazy system. I've put it here https://www.mediacours.com/tb_examples/newrequest2/ and the zip is here https://www.mediacours.com/tb_examples/newrequest2.zip.

John
Last edited by John Robin Dove on Wed Mar 22, 2023 8:23 am, edited 1 time in total.
John Robin Dove
 
Posts: 486
Joined: Thu Jan 23, 2014 4:35 am

Re: JavaScript Promises

Postby Clifton » Tue Mar 21, 2023 11:58 am

This development is continuing to look very nice. The revision is even better than the first attempt—which is usually the case with any coding work.
Clifton
Site Admin
 
Posts: 732
Joined: Tue Jan 14, 2014 1:04 am

Re: JavaScript Promises

Postby John Robin Dove » Tue Mar 28, 2023 9:44 am

Hi Clifton,
I have a problem. Bit by bit, I am rewriting my program using the 'doStages' system which has the advantage of doing tasks in a linear fashion. My aim is to make it possible for someone other tham me to figure out how things work. I think this is possible if I use the new system but I've hit a snag. I find that in a longer sequence, tasks can only be carried out once. I have a sequence that asks the user to define the school year by entering the start month. This works fine but only ONCE! If you change your mind and decide that the school year starts in August and not in September the data is not saved the second time around. If you quit the page, go to a different page and come back, the system works again. I've even tried reloading the page in the iframe automatically but that doesn't work either!

I think it must be a garbage collection problem. I have been reading various articles about this but I haven't been able to find a solution because I don't really understand what's happening. I'm hoping you will. Heres the code that causes the problem.
Code: Select all
/*
//This is the minified bit below.
  var stage = 0;
    function sleep(ms)
    {
    return new Promise(resolve => setTimeout(resolve, ms));
    }

    const doPause = async() =>
    {
    var on = false;
      while (!(keys.reply))
      {
        if (on == false)
        {
        on = true;
        await sleep(200);
          if (!(keys.reply))
          {
          on = false;
          }
        }
      }
    on = false;
    stage += 1;
    doStages()
    }
    ***/
  <function name="setSchoolYear" event="" params="">
  <![CDATA[
  var stage=0;function sleep(e){return new Promise(s=>setTimeout(s,e))}const doPause=async()=>{for(var e=!1;!top.keys.reply;)0==e&&(e=!0,await sleep(200),top.keys.reply||(e=!1));e=!1,stage+=1,doStages()};
  var replyDetail = [];
  var n;
  var caseNo;
    const doStages = function()
    {
      if (stage == 1) //Reply to question about school year corresponding to calendar year or not.
      {
      var yes = tbfunction_pgExecuteRemote("main", "tbfunction_pgTBObjSet", ["keys", "splitter", "190`1"]); //Yes
        if (top.keys.reply == yes)
        {
        caseNo = 1;  //School Year corresponds to calendar year.
        sharedActions.makeYearFolder(0); // month 0 is code for school year is calendar year. e.g. 2023  instead of 2022_23
        }
        else
        {
        caseNo = 2;
        tbfunction_pgExecuteRemote("main", "tbfunction_pgTBObjSet", ["keys", "input", ["344`2", "1"]]);   //Click on the first month of the school year.
        tbfunction_pgTBObjSet("container1", "visible", true);
        tbfunction_pgTBObjSet("yearGroup", "visible", true);
        }
      doPause();
      }
      else if (stage == 2) //case 1 If year folder has been made, saveFile updates start.dat file which contains monthNo, grade system and language and these are set in mdc_in.
      {
        if (caseNo == 1)
        {
        replyDetail = tbfunction_pgSplitToArray(top.keys.reply, "^");
        n = /success/i.test(replyDetail[1]); 
          if (replyDetail[0] == 16 && n == true)
          {
          sharedActions.saveFile();
          doPause()
          }
        }
        else  //case 2
        {
          if (top.keys.reply == "OK") //Month has been selected.
          {
          top.keys.reply = "";
          doPause()
          }
        }
      }
      else if (stage == 3)  //case 1 ends here, case 2 makes year folder
      {
      replyDetail = tbfunction_pgSplitToArray(top.keys.reply, "^");
        if (caseNo == 1)
        {
          if (top.keys.reply != "unfinished")  //data is not saved until school year, grading system and interface language have been defined.
          {
            if (replyDetail[0] != 11 || pgIsNumber(replyDetail[1]) == false)
            {
            tbfunction_pgExecuteRemote("main", "tbfunction_pgTBObjSet", ["keys", "showError", ["Saving data to start.dat file.", top.keys.reply]]);
            }
          }
        stage = 0;
        return; //End of case 1
        }
        else
        {
        sharedActions.makeYearFolder(top.keys.reply);
        doPause();
        }
      }
      else if (stage == 4) // Only case 2 now. Save data.
      {
      replyDetail = tbfunction_pgSplitToArray(top.keys.reply, "^");
        if (replyDetail[0] == 16)
        {
        n = /success/i.test(replyDetail[1]);
          if (n != true)
          {
          tbfunction_pgExecuteRemote("main", "tbfunction_pgTBObjSet", ["keys", "showError", ["Creating current year folder", top.keys.reply]]);
          stage = 0;
          return;
          }
          else
          {
          sharedActions.saveFile();
          doPause()
          }
        }
        else
        {
        tbfunction_pgExecuteRemote("main", "tbfunction_pgTBObjSet", ["keys", "showError", ["Creating current year folder", top.keys.reply]]);
        stage = 0;
        return; 
        }
      }
      else if (stage == 5) // End of case 2
      {
      replyDetail = tbfunction_pgSplitToArray(top.keys.reply, "^");
        if (top.keys.reply != "unfinished")
        {
        var replyDetail = tbfunction_pgSplitToArray(top.keys.reply, "^");     
          if (replyDetail[0] != 11 || pgIsNumber(replyDetail[1]) == false)
          {
          tbfunction_pgExecuteRemote("main", "tbfunction_pgTBObjSet", ["keys", "showError", ["Saving data to start.dat file.", top.keys.reply]]);
          }
        }
      return;
      }
    }
  //=============================================================stage 0==========================================================================================================================================-
  tbfunction_pgExecuteRemote("main", "tbfunction_pgTBObjSet", ["keys", "input", ["346`2", "180`1", "190`1"]]); //Does the school year correspond to the calendar year (mainly countries in the southern hemisphere)?
  doPause();
  ]]>
  </function>
 
  <function name="makeYearFolder" event="" params="monthNo">
  <![CDATA[
  var currentYear = sharedActions.getCurrentYear(monthNo); //if monthNo  is not 0 the current year will be something like 2022_23
  top.keys.sendToServer("makeFoldersS.php",["endPath", "fNo"], [sharedActions.VArray[1] + "/" + sharedActions.VArray[2] + "/" + currentYear +"/", "16"]);     
  sharedActions.VArray[5] = monthNo;
  ]]>
  </function>


I'm not getting any error messages. Execution seems to just fade away if you attempt to run the sequence more than once.
Thanks for your time.
John
UPDATE I think I'm barking up the wrong tree here. I don't think it's anythything to do with garbage collection after all. So don't waste any time on it.
UPDATE2 Solved! But this might have gone on even longer because it wasn't my code causing the (false) problem but PSPad which got tired after 2 changes and decided not to update! I won't use it any more. I have just discovered that you have included a much better looking file editor in the Powerpac menu. I'll try that tomorrow. :)
Last edited by John Robin Dove on Tue Mar 28, 2023 1:21 pm, edited 1 time in total.
John Robin Dove
 
Posts: 486
Joined: Thu Jan 23, 2014 4:35 am

Re: JavaScript Promises

Postby Clifton » Tue Mar 28, 2023 1:16 pm

At first glance I believe an issue may be raised in that you are using const to assign your async function. While this may be required for promises to work, the declaration should not be made where it will be redeclared repeatedly. Once a const is assigned it cannot be destroyed or changed. Elements within it can be changed but the declaration must remain unchanged.

So, if this is the case, when you run the loop the first time it appears to work. However, the second time the script bombs when it encounters the const when it attempts to redefine an already defined const assignment.

I would recommend putting your constr assignments in a book.xml or somewhere else where the assignment occurs only once when the application loads. Then you may be able to call the async functions freely without issue.

Haven't tested this theory, so this is just a first glance possibility.
Clifton
Clifton
Site Admin
 
Posts: 732
Joined: Tue Jan 14, 2014 1:04 am

Re: JavaScript Promises

Postby John Robin Dove » Tue Mar 28, 2023 1:23 pm

Thanks but as I mentioned above this was a false alarm.
John Robin Dove
 
Posts: 486
Joined: Thu Jan 23, 2014 4:35 am

Re: JavaScript Promises

Postby John Robin Dove » Mon Apr 03, 2023 11:23 am

Hi Clifton,
I've found a real problem this time. I discovered that the sleep loop continues if the global variable sharedActions.reply remains null. It even continues if you quit the page! I didn't think that was possible. As you may remember, my pages are all shown in iframes using pgGotoURL, If, for some reason, no reply is given by the user, the loop continues with its variables on and stage and, if you close the page and then open it again, it's still there! If you try and use it again, all kinds of errors may occur because the variable stage will have been incremented. I found a way to prevent this happening quite quickly but it took me a while longer to make an example to illustrate the problem.

The problem was, in fact, just waiting to happen in my two previous examples. If you clicked on the 'OK' button without entering anything, the loop just went on and on. I have put a corrected version here https://www.mediacours.com/tb_examples/newrequest3/ with a zip here https://www.mediacours.com/tb_examples/newrequest3.zip

John
John Robin Dove
 
Posts: 486
Joined: Thu Jan 23, 2014 4:35 am

Re: JavaScript Promises

Postby Clifton » Sun May 14, 2023 2:18 pm

Hi John,

I was just reading through these posts again and figured that one reason why promises stay active even after leaving the page is because ToolBook apps only reload the iframe content on page navigation. More than likely you are running your promise code in the main window (top level). This is probably NOT what you want in many cases, since the currently loaded page is where the impact of the code will apply. If you move the functions to the page level, or even an object level, then once the promise is fulfilled make sure to destroy the promise object, you may be fine.

Haven't tested this theory and you have likely enhanced your code many times over since your last post.

Clifton
Clifton
Site Admin
 
Posts: 732
Joined: Tue Jan 14, 2014 1:04 am


Return to General Discussion

Who is online

Users browsing this forum: No registered users and 3 guests

cron