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.