Difference between revisions of "pas2js AsyncAWait"

From Lazarus wiki
(Example Wait 2 Seconds)
(AWait)
 
(5 intermediate revisions by the same user not shown)
Line 32: Line 32:
 
function await(AsyncFunctionOfResultTypeT): T;
 
function await(AsyncFunctionOfResultTypeT): T;
 
function await(aType; p: TJSPromise): aType; // explicit promise requires the resolved type
 
function await(aType; p: TJSPromise): aType; // explicit promise requires the resolved type
function await(const Expr: T): T; // with T not a TJSPromise
+
function await(aType; v: JSValue): aType; // explicit optional promise requires the resolved type
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Line 39: Line 39:
 
An '''async''' procedure can contain multiple '''await''' calls.
 
An '''async''' procedure can contain multiple '''await''' calls.
  
Giving a promise to ''await'' works as if the following code is inside a TJSPromise._then(). If the promise is already resolved, ''await'' returns the value immediately. Otherwise the promise is returned as the ''async'' function result and the rest of the ''async'' function is executed when the promise is resolved.
+
Giving a promise to ''await'' works as if the following code is inside a ''TJSPromise._then()''. If the promise is already resolved, ''await'' returns the value immediately. Otherwise the promise is returned as the ''async'' function result and the rest of the ''async'' function is executed when the promise is resolved.
 
   
 
   
You can enclose an '''await''' function in ''try..finally'' or ''try..except'' for the case the ''TJSPromise'' raises an exception.
+
You can enclose an '''await''' function in ''try..finally'' or ''try..except'' for the case the ''Promise'' raises an exception.
  
You can call ''await'' on any expression, e.g. ''await(1)'', but ''await'' has only an effect if the expression gives a ''TJSPromise''.  
+
You can call ''await'' on a jsvalue, any promise will be resolved. For example when a JS function returns either a number or a promise of the number.
 +
 
 +
You can define your own external class 'Promise'. It does not need to be called ''TJSPromise''.
  
 
==Example Async try fetch==
 
==Example Async try fetch==
Line 137: Line 139:
 
===Manually created promise Version 2===
 
===Manually created promise Version 2===
  
For type safety if the value type is known, then you should always use it as result type:
+
Alternatively you can use the promise result type (here: string), by passing the ''TJSPromise'' to the '''exit()''' function:
  
 
<syntaxhighlight lang="pascal">
 
<syntaxhighlight lang="pascal">
Line 144: Line 146:
 
uses JS, Web;
 
uses JS, Web;
  
function ResolveAfter2Seconds: string;
+
function ResolveAfter2Seconds: string; async;
 
// returns immediately with a Promise,
 
// returns immediately with a Promise,
 
// which after 2 seconds gets resolved
 
// which after 2 seconds gets resolved
 +
var p: TJSPromise;
 
begin
 
begin
   Result:=TJSPromise.new(procedure(resolve, reject : TJSPromiseResolver)
+
   p:=TJSPromise.new(procedure(resolve, reject : TJSPromiseResolver)
 
     begin
 
     begin
 
     window.setTimeout(procedure
 
     window.setTimeout(procedure
Line 156: Line 159:
 
       2000); // wait 2 seconds
 
       2000); // wait 2 seconds
 
     end);
 
     end);
 +
  exit(p); // the exit() function takes either a TJSPromise or a string
 
end;
 
end;
  
Line 162: Line 166:
 
begin
 
begin
 
   writeln('calling');
 
   writeln('calling');
   s := await(resolveAfter2Seconds());
+
   s := await(resolveAfter2Seconds()); // no "string" needed
 
   // the await pauses this procedure returning to the caller
 
   // the await pauses this procedure returning to the caller
 
   // when the Promise from resolveAfter2Seconds gets resolved  
 
   // when the Promise from resolveAfter2Seconds gets resolved  
Line 176: Line 180:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
resolved
+
Note: The '''Result''' variable is of type string. You cannot assign it a promise.
  
 
==Example Multi await==
 
==Example Multi await==

Latest revision as of 12:47, 31 December 2020

Overview

Pas2js supports the JS operators async/await to simplify the use of Promise, which itself simplifies writing asynchronous code, like starting a download, waiting for the result, then depending on the result start the next and so forth. Instead of writing a tedious and hard to read mass of sub functions an async procedure can be written in the common serial aka imperative way.

Basics

First of we have the async keyword, which you add as a procedure type modifier to turn it into an async function (or async procedure). An async function knows how to expect the possibility of the await function being used to invoke asynchronous code.

Usually you call an async function via the await function. To explain the inner workings it is useful to see what happens when you call an async function directly. Then you get a TJSPromise:

uses JS;

function Run: word; async;
begin
  Result:=3;
end;

var p: TJSPromise;
begin
  p:=Run(); // calling directly without await() returns a TJSPromise!
end.

That's because an async function runs till an await and returns. If there is no await it works like a normal function, returning immediately a resolved TJSPromise. If it contains one or more await calls, each await pauses the execution of the async function and continues it asynchronously (single threaded) later. A TJSPromise has a hook for success and one for fail, which usually requires writing some anonymous functions. With async/await you can write much more readable code.

AWait

Pas2js provides a built-in await function, which supports three flavours:

function await(AsyncFunctionOfResultTypeT): T;
function await(aType; p: TJSPromise): aType; // explicit promise requires the resolved type
function await(aType; v: JSValue): aType; // explicit optional promise requires the resolved type

The await function can only be used inside a procedure with the async modifier.

An async procedure can contain multiple await calls.

Giving a promise to await works as if the following code is inside a TJSPromise._then(). If the promise is already resolved, await returns the value immediately. Otherwise the promise is returned as the async function result and the rest of the async function is executed when the promise is resolved.

You can enclose an await function in try..finally or try..except for the case the Promise raises an exception.

You can call await on a jsvalue, any promise will be resolved. For example when a JS function returns either a number or a promise of the number.

You can define your own external class 'Promise'. It does not need to be called TJSPromise.

Example Async try fetch

Example demonstrating waiting for an async function, catching errors with a try..except and no anonymous functions.

// Translated from https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await
program tryfetch;

{$mode objfpc}

uses
  browserconsole, JS, Web, SysUtils;

procedure myFetch; async;
var
  response: TJSResponse;
  myBlob: TJSBlob;
  image: TJSHTMLImageElement;
  objectURL: string;
begin
  try
    response := await(window.fetch('test1.png'));

    if not response.ok then
      raise Exception.Create('HTTP error! status: '+str(response.status))
    else begin
      myBlob := await(response.blob());
      objectURL := TJSURL.createObjectURL(myBlob);
      image := TJSHTMLImageElement(document.createElement('img'));
      image.src := objectURL;
      document.body.appendChild(image);
    end;
  except
    console.log(JSExceptValue);
  end;
end;

begin
  myFetch;
end.

Example Manually created promise

Example demonstrating using await with a non async function, creating the TJSPromise manually:

Manually created promise Version 1

Program MyModule;

uses JS, Web;

function ResolveAfter2Seconds: TJSPromise;
// returns immediately with a Promise,
// which after 2 seconds gets resolved
begin
  Result:=TJSPromise.new(procedure(resolve, reject : TJSPromiseResolver)
    begin
    window.setTimeout(procedure
      begin
      resolve('resolved');
      end,
      2000); // wait 2 seconds
    end);
end;

procedure AsyncCall; async;
var s: string;
begin
  writeln('calling');
  s := await(string,resolveAfter2Seconds());
  // the await pauses this procedure returning to the caller
  // when the Promise from resolveAfter2Seconds gets resolved 
  // this procedure is continued
  writeln(s); // expected output: 'resolved'
end;

begin
  AsyncCall;
  // calling AsyncCall returns immediately, while the Promise is waiting
  writeln('called');
end.

Expected output:

calling
called
resolved

Manually created promise Version 2

Alternatively you can use the promise result type (here: string), by passing the TJSPromise to the exit() function:

Program MyModule;

uses JS, Web;

function ResolveAfter2Seconds: string; async;
// returns immediately with a Promise,
// which after 2 seconds gets resolved
var p: TJSPromise;
begin
  p:=TJSPromise.new(procedure(resolve, reject : TJSPromiseResolver)
    begin
    window.setTimeout(procedure
      begin
      resolve('resolved');
      end,
      2000); // wait 2 seconds
    end);
  exit(p); // the exit() function takes either a TJSPromise or a string
end;

procedure AsyncCall; async;
var s: string;
begin
  writeln('calling');
  s := await(resolveAfter2Seconds()); // no "string" needed
  // the await pauses this procedure returning to the caller
  // when the Promise from resolveAfter2Seconds gets resolved 
  // this procedure is continued
  writeln(s); // expected output: 'resolved'
end;

begin
  AsyncCall;
  // calling AsyncCall returns immediately, while the Promise is waiting
  writeln('called');
end.

Note: The Result variable is of type string. You cannot assign it a promise.

Example Multi await

Example demonstrating starting multiple async functions and waiting for them while they execute in parallel.

// Translated from https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await
program tryfetchmany;

{$mode objfpc}

uses
  browserconsole, JS, Web, SysUtils, Types;

function FetchBlob(url: string): TJSBlob; async;
var
  response: TJSResponse;
begin
  response := await(window.fetch(url));
  if not response.ok then
    raise Exception.create('HTTP error! status: '+str(response.status))
  else
    Result:=await(response.blob());
end;

function FetchText(url: string): string; async;
var
  response: TJSResponse;
begin
  response := await(window.fetch(url));
  if not response.ok then
    raise Exception.create('HTTP error! status: '+str(response.status))
  else
    Result:=await(response.text());
end;

procedure DisplayContent; async;
var
  coffee, tea, description: TJSPromise;
  objectURL1, objectURL2, descText: String;
  values: TJSValueDynArray;
  image1, image2: TJSHTMLImageElement;
  para: TJSHTMLElement;
begin
  try
    coffee := FetchBlob('test1.png');
    tea := FetchBlob('test1.png');
    description := FetchText('description.txt');

    values := await(TJSValueDynArray,TJSPromise.all([coffee, tea, description]));

    objectURL1 := TJSURL.createObjectURL(values[0]);
    objectURL2 := TJSURL.createObjectURL(values[1]);
    descText := string(values[2]);

    image1 := TJSHTMLImageElement(document.createElement('img'));
    image2 := TJSHTMLImageElement(document.createElement('img'));
    image1.src := objectURL1;
    image2.src := objectURL2;
    document.body.appendChild(image1);
    document.body.appendChild(image2);

    para := TJSHTMLElement(document.createElement('p'));
    para.textContent := descText;
    document.body.appendChild(para);

  except
    writeln(JSExceptValue);
  end;
end;

begin
  DisplayContent;
end.

Navigation