EWB Experiments with JavaScript Async/Await

Modern JavaScript has asynch-ronous functions which can appear to block while they wait for the await to work.

Internally, JavaScript uses promises which can be handled in one of two ways

  • having separate OnComplete-type functions like TServerRequest uses

  • having lamba functions which executes at a later time.

But with Async functions you can just saw await and the code appears to block until the condition or an error occurs.

The await function generates a run-time error if you use it in a normal function, it only works in functions defined as async.

Also know that EWB does not normally let you define async functions without manually editing the generated EWB JavaScript file, or make a program to edit that file after each compile.

But I thought it would be fun to see if EWB could be used to generate async functions without use of external files or editing the JavaScript compiled output. This post shows how to do that.

Defining the Async Function type

First we define a function which returns a prototypt to an async function. It takes two parameters, a name and the fn as JavaScript in a string:

type
   external TStrStrFn = function( name : string ; fn : string ):variant;

var
  external AsyncFunction : TStrStrFn;

This code implements the AsyncFunction.

AsyncFunction := TStrStrFn(variant(CreateObject('Object.getPrototypeOf(async function(){}).constructor')));

Creating our Async Function

Now that we can define the function, this code defines a particular async function named fetchpage. fn is the string of JavaScript it will execute.

fetchpage := TAsyncStrFn(variant(CreateObject( 'new AsyncFunction("url","'+fn+'")')));

The JavaScript executed Asynchronously

This short routine pops up a message box, then uses fetch and then extracts the text, and eventually calls an EWB function to write the web page (xxx) into a MultiLineEdit, and is peppered with logging to the console screen.

window.alert("started");
let response = await fetch(globalurl);
let xxx = await response.text();
console.log("url download complete");
globalshowasyncform(xxx);
console.log("display updated")

Calling our Async Function

We can call it from EWB and pass a parameter. So we will call it with the page’s own URL.

   fetchpage( window.location.href );

Console.Log

Console.log( s : string) writes stuff to the debug console. If you enable debugging in your browser and look at the console, you’ll see the output. Note the orderring of results - the data is downloaded and displayed after the main thread exits.

Setting up ASync function
done main thread
url download complete
display updated

Ramifications

This experiment shows that EWB functions can be used with and by async functions. It’s currently a bit awkward and you have to write a bit of JavaScript by hand, but it is cool.

The Complete Code

unit asyncfn;

interface

uses WebCore, Webdom, WebUI, WebForms, WebCtrls, WebEdits;

type
  external TConsole = class ( TExternalObject )
  public
    procedure log( s : string );
  end;

   TForm1 = class(TForm)
      MultiLineEdit1: TMultiLineEdit;
      procedure Form1Show(Sender: TObject);
   private
      { Private declarations }
      procedure Setup;
   public
      { Public declarations }

   end;

var
   Form1: TForm1;
   external console : TConsole;

implementation

type
  external TStrStrFn = function( name : string ; fn : string ):variant;
  external TASYNCStrFn = function( str : string ):variant;
  external TProcStr = procedure( s : string ) ;
var
  external AsyncFunction : TStrStrFn;
  external fetchpage : TASYNCStrFn;
  external globalv : variant;
  external globalshowasyncform : TProcStr;
  external globalresolve : TASYNCStrFn;
  external globalmyasyncfunction : TProcStr;

procedure showasyncform( xxx : string );
begin
  Form1.multilineEdit1.Lines.Text := xxx;
end;

procedure SetupAsyncFunction;
begin
   AsyncFunction := TStrStrFn(variant(CreateObject('Object.getPrototypeOf(async function(){}).constructor')));
end;

procedure TForm1.Setup;
var
  fn : string;
  v : variant;
begin
   globalshowasyncform := showasyncform;
   SetupAsyncFunction;

   globalurl := window.location.href; // eg. 'http://10.211.55.3/testasyncfn.html';
   fn := 'window.alert(\"started\");              '+
         'let response = await fetch(globalurl);  '+
         'let xxx = await response.text();        '+
         'console.log(\"url download complete\"); '+
         'globalshowasyncform(xxx);               '+
         'console.log(\"display updated\")        ';

   console.log('Setting up ASync function');
   // this creates fetchpage, an async function
   fetchpage := TAsyncStrFn(variant(CreateObject( 'new AsyncFunction("url","'+fn+'")')));

   // now call it
   fetchpage( window.location.href );
   console.log('done main thread');
end;


procedure TForm1.Form1Show(Sender: TObject);
begin
  Setup;
end;

end.