Sign in Join
Wiki copytemporary

pas2js AsyncAWait: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: pas2js AsyncAWait

OldNewLine
1+# Overview
2+
3+[[pas2js|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.
4+
5+# Basics
6+
7+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.
8+
9+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**:
10+
11+``` pascal
12+uses JS;
13+
14+function Run: word; async;
15+begin
16+ Result:=3;
17+end;
18+
19+var p: TJSPromise;
20+begin
21+ p:=Run(); // calling directly without await() returns a TJSPromise!
22+end.
23+```
24+
25+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.
26+
27+# AWait
28+
29+Pas2js provides a built-in **await** function, which supports three flavours:
30+
31+``` pascal
32+function await(AsyncFunctionOfResultTypeT): T;
33+function await(aType; p: TJSPromise): aType; // explicit promise requires the resolved type
34+function await(aType; v: JSValue): aType; // explicit optional promise requires the resolved type
35+```
36+
37+The **await** function can only be used inside a procedure with the **async** modifier.
38+
39+An **async** procedure can contain multiple **await** calls.
40+
41+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.
42+
43+You can enclose an **await** function in *try..finally* or *try..except* for the case the *Promise* raises an exception.
44+
45+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.
46+
47+You can define your own external class 'Promise'. It does not need to be called *TJSPromise*.
48+
49+## Example Async try fetch
50+
51+Example demonstrating waiting for an async function, catching errors with a try..except and no anonymous functions.
52+
53+``` pascal
54+// Translated from https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await
55+program tryfetch;
56+
57+{$mode objfpc}
58+
59+uses
60+ browserconsole, JS, Web, SysUtils;
61+
62+procedure myFetch; async;
63+var
64+ response: TJSResponse;
65+ myBlob: TJSBlob;
66+ image: TJSHTMLImageElement;
67+ objectURL: string;
68+begin
69+ try
70+ response := await(window.fetch('test1.png'));
71+
72+ if not response.ok then
73+ raise Exception.Create('HTTP error! status: '+str(response.status))
74+ else begin
75+ myBlob := await(response.blob());
76+ objectURL := TJSURL.createObjectURL(myBlob);
77+ image := TJSHTMLImageElement(document.createElement('img'));
78+ image.src := objectURL;
79+ document.body.appendChild(image);
80+ end;
81+ except
82+ console.log(JSExceptValue);
83+ end;
84+end;
85+
86+begin
87+ myFetch;
88+end.
89+```
90+
91+## Example Manually created promise
92+
93+Example demonstrating using **await** with a non async function, creating the TJSPromise manually:
94+
95+### Manually created promise Version 1
96+
97+``` pascal
98+Program MyModule;
99+
100+uses JS, Web;
101+
102+function ResolveAfter2Seconds: TJSPromise;
103+// returns immediately with a Promise,
104+// which after 2 seconds gets resolved
105+begin
106+ Result:=TJSPromise.new(procedure(resolve, reject : TJSPromiseResolver)
107+ begin
108+ window.setTimeout(procedure
109+ begin
110+ resolve('resolved');
111+ end,
112+ 2000); // wait 2 seconds
113+ end);
114+end;
115+
116+procedure AsyncCall; async;
117+var s: string;
118+begin
119+ writeln('calling');
120+ s := await(string,resolveAfter2Seconds());
121+ // the await pauses this procedure returning to the caller
122+ // when the Promise from resolveAfter2Seconds gets resolved
123+ // this procedure is continued
124+ writeln(s); // expected output: 'resolved'
125+end;
126+
127+begin
128+ AsyncCall;
129+ // calling AsyncCall returns immediately, while the Promise is waiting
130+ writeln('called');
131+end.
132+```
133+
134+Expected output:
135+
136+`calling`\
137+`called`\
138+`resolved`
139+
140+### Manually created promise Version 2
141+
142+Alternatively you can use the promise result type (here: string), by passing the *TJSPromise* to the **exit()** function:
143+
144+``` pascal
145+Program MyModule;
146+
147+uses JS, Web;
148+
149+function ResolveAfter2Seconds: string; async;
150+// returns immediately with a Promise,
151+// which after 2 seconds gets resolved
152+var p: TJSPromise;
153+begin
154+ p:=TJSPromise.new(procedure(resolve, reject : TJSPromiseResolver)
155+ begin
156+ window.setTimeout(procedure
157+ begin
158+ resolve('resolved');
159+ end,
160+ 2000); // wait 2 seconds
161+ end);
162+ exit(p); // the exit() function takes either a TJSPromise or a string
163+end;
164+
165+procedure AsyncCall; async;
166+var s: string;
167+begin
168+ writeln('calling');
169+ s := await(resolveAfter2Seconds()); // no "string" needed
170+ // the await pauses this procedure returning to the caller
171+ // when the Promise from resolveAfter2Seconds gets resolved
172+ // this procedure is continued
173+ writeln(s); // expected output: 'resolved'
174+end;
175+
176+begin
177+ AsyncCall;
178+ // calling AsyncCall returns immediately, while the Promise is waiting
179+ writeln('called');
180+end.
181+```
182+
183+Note: The **Result** variable is of type string. You cannot assign it a promise.
184+
185+## Example Multi await
186+
187+Example demonstrating starting multiple async functions and waiting for them while they execute in parallel.
188+
189+``` pascal
190+// Translated from https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await
191+program tryfetchmany;
192+
193+{$mode objfpc}
194+
195+uses
196+ browserconsole, JS, Web, SysUtils, Types;
197+
198+function FetchBlob(url: string): TJSBlob; async;
199+var
200+ response: TJSResponse;
201+begin
202+ response := await(window.fetch(url));
203+ if not response.ok then
204+ raise Exception.create('HTTP error! status: '+str(response.status))
205+ else
206+ Result:=await(response.blob());
207+end;
208+
209+function FetchText(url: string): string; async;
210+var
211+ response: TJSResponse;
212+begin
213+ response := await(window.fetch(url));
214+ if not response.ok then
215+ raise Exception.create('HTTP error! status: '+str(response.status))
216+ else
217+ Result:=await(response.text());
218+end;
219+
220+procedure DisplayContent; async;
221+var
222+ coffee, tea, description: TJSPromise;
223+ objectURL1, objectURL2, descText: String;
224+ values: TJSValueDynArray;
225+ image1, image2: TJSHTMLImageElement;
226+ para: TJSHTMLElement;
227+begin
228+ try
229+ coffee := FetchBlob('test1.png');
230+ tea := FetchBlob('test1.png');
231+ description := FetchText('description.txt');
232+
233+ values := await(TJSValueDynArray,TJSPromise.all([coffee, tea, description]));
234+
235+ objectURL1 := TJSURL.createObjectURL(values[0]);
236+ objectURL2 := TJSURL.createObjectURL(values[1]);
237+ descText := string(values[2]);
238+
239+ image1 := TJSHTMLImageElement(document.createElement('img'));
240+ image2 := TJSHTMLImageElement(document.createElement('img'));
241+ image1.src := objectURL1;
242+ image2.src := objectURL2;
243+ document.body.appendChild(image1);
244+ document.body.appendChild(image2);
245+
246+ para := TJSHTMLElement(document.createElement('p'));
247+ para.textContent := descText;
248+ document.body.appendChild(para);
249+
250+ except
251+ writeln(JSExceptValue);
252+ end;
253+end;
254+
255+begin
256+ DisplayContent;
257+end.
258+```
259+
260+# Navigation
261+
262+- [[pas2js|Pas2js]]
263+
264+---
265+*From the Free Pascal wiki, page "pas2js AsyncAWait", as it stood on 2020-12-31 after a change by Mattias2, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
266+Category: Pas2js

Back to history