Sign in Join
Wiki copytemporary

pas2js AsyncAWait: what changed

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

OldNewLine
11# Overview
22
33[[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.
44
55# Basics
66
77First 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.
88
99Usually 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**:
1010
1111``` pascal
1212uses JS;
1313
1414function Run: word; async;
1515begin
1616 Result:=3;
1717end;
1818
1919var p: TJSPromise;
2020begin
2121 p:=Run(); // calling directly without await() returns a TJSPromise!
2222end.
2323```
2424
2525That'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.
2626
2727# AWait
2828
2929Pas2js provides a built-in **await** function, which supports three flavours:
3030
3131``` pascal
3232function await(AsyncFunctionOfResultTypeT): T;
3333function await(aType; p: TJSPromise): aType; // explicit promise requires the resolved type
3434function await(aType; v: JSValue): aType; // explicit optional promise requires the resolved type
3535```
3636
3737The **await** function can only be used inside a procedure with the **async** modifier.
3838
3939An **async** procedure can contain multiple **await** calls.
4040
4141Giving 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.
4242
4343You can enclose an **await** function in *try..finally* or *try..except* for the case the *Promise* raises an exception.
4444
4545You 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.
4646
4747You can define your own external class 'Promise'. It does not need to be called *TJSPromise*.
4848
4949## Example Async try fetch
5050
5151Example demonstrating waiting for an async function, catching errors with a try..except and no anonymous functions.
5252
5353``` pascal
5454// Translated from https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await
5555program tryfetch;
5656
5757{$mode objfpc}
5858
5959uses
6060 browserconsole, JS, Web, SysUtils;
6161
6262procedure myFetch; async;
6363var
6464 response: TJSResponse;
6565 myBlob: TJSBlob;
6666 image: TJSHTMLImageElement;
6767 objectURL: string;
6868begin
6969 try
7070 response := await(window.fetch('test1.png'));
7171
7272 if not response.ok then
7373 raise Exception.Create('HTTP error! status: '+str(response.status))
7474 else begin
7575 myBlob := await(response.blob());
7676 objectURL := TJSURL.createObjectURL(myBlob);
7777 image := TJSHTMLImageElement(document.createElement('img'));
7878 image.src := objectURL;
7979 document.body.appendChild(image);
8080 end;
8181 except
8282 console.log(JSExceptValue);
8383 end;
8484end;
8585
8686begin
8787 myFetch;
8888end.
8989```
9090
9191## Example Manually created promise
9292
9393Example demonstrating using **await** with a non async function, creating the TJSPromise manually:
9494
9595### Manually created promise Version 1
9696
9797``` pascal
9898Program MyModule;
9999
100100uses JS, Web;
101101
102102function ResolveAfter2Seconds: TJSPromise;
103103// returns immediately with a Promise,
104104// which after 2 seconds gets resolved
105105begin
106106 Result:=TJSPromise.new(procedure(resolve, reject : TJSPromiseResolver)
107107 begin
108108 window.setTimeout(procedure
109109 begin
110110 resolve('resolved');
111111 end,
112112 2000); // wait 2 seconds
113113 end);
114114end;
115115
116116procedure AsyncCall; async;
117117var s: string;
118118begin
119119 writeln('calling');
120120 s := await(string,resolveAfter2Seconds());
121121 // the await pauses this procedure returning to the caller
122122 // when the Promise from resolveAfter2Seconds gets resolved
123123 // this procedure is continued
124124 writeln(s); // expected output: 'resolved'
125125end;
126126
127127begin
128128 AsyncCall;
129129 // calling AsyncCall returns immediately, while the Promise is waiting
130130 writeln('called');
131131end.
132132```
133133
134134Expected output:
135135
136-`calling`\
137-`called`\
138-`resolved`
136+```text
137+calling
138+called
139+resolved
140+```
139141
140142### Manually created promise Version 2
141143
142144Alternatively you can use the promise result type (here: string), by passing the *TJSPromise* to the **exit()** function:
143145
144146``` pascal
145147Program MyModule;
146148
147149uses JS, Web;
148150
149151function ResolveAfter2Seconds: string; async;
150152// returns immediately with a Promise,
151153// which after 2 seconds gets resolved
152154var p: TJSPromise;
153155begin
154156 p:=TJSPromise.new(procedure(resolve, reject : TJSPromiseResolver)
155157 begin
156158 window.setTimeout(procedure
157159 begin
158160 resolve('resolved');
159161 end,
160162 2000); // wait 2 seconds
161163 end);
162164 exit(p); // the exit() function takes either a TJSPromise or a string
163165end;
164166
165167procedure AsyncCall; async;
166168var s: string;
167169begin
168170 writeln('calling');
169171 s := await(resolveAfter2Seconds()); // no "string" needed
170172 // the await pauses this procedure returning to the caller
171173 // when the Promise from resolveAfter2Seconds gets resolved
172174 // this procedure is continued
173175 writeln(s); // expected output: 'resolved'
174176end;
175177
176178begin
177179 AsyncCall;
178180 // calling AsyncCall returns immediately, while the Promise is waiting
179181 writeln('called');
180182end.
181183```
182184
183185Note: The **Result** variable is of type string. You cannot assign it a promise.
184186
185187## Example Multi await
186188
187189Example demonstrating starting multiple async functions and waiting for them while they execute in parallel.
188190
189191``` pascal
190192// Translated from https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await
191193program tryfetchmany;
192194
193195{$mode objfpc}
194196
195197uses
196198 browserconsole, JS, Web, SysUtils, Types;
197199
198200function FetchBlob(url: string): TJSBlob; async;
199201var
200202 response: TJSResponse;
201203begin
202204 response := await(window.fetch(url));
203205 if not response.ok then
204206 raise Exception.create('HTTP error! status: '+str(response.status))
205207 else
206208 Result:=await(response.blob());
207209end;
208210
209211function FetchText(url: string): string; async;
210212var
211213 response: TJSResponse;
212214begin
213215 response := await(window.fetch(url));
214216 if not response.ok then
215217 raise Exception.create('HTTP error! status: '+str(response.status))
216218 else
217219 Result:=await(response.text());
218220end;
219221
220222procedure DisplayContent; async;
221223var
222224 coffee, tea, description: TJSPromise;
223225 objectURL1, objectURL2, descText: String;
224226 values: TJSValueDynArray;
225227 image1, image2: TJSHTMLImageElement;
226228 para: TJSHTMLElement;
227229begin
228230 try
229231 coffee := FetchBlob('test1.png');
230232 tea := FetchBlob('test1.png');
231233 description := FetchText('description.txt');
232234
233235 values := await(TJSValueDynArray,TJSPromise.all([coffee, tea, description]));
234236
235237 objectURL1 := TJSURL.createObjectURL(values[0]);
236238 objectURL2 := TJSURL.createObjectURL(values[1]);
237239 descText := string(values[2]);
238240
239241 image1 := TJSHTMLImageElement(document.createElement('img'));
240242 image2 := TJSHTMLImageElement(document.createElement('img'));
241243 image1.src := objectURL1;
242244 image2.src := objectURL2;
243245 document.body.appendChild(image1);
244246 document.body.appendChild(image2);
245247
246248 para := TJSHTMLElement(document.createElement('p'));
247249 para.textContent := descText;
248250 document.body.appendChild(para);
249251
250252 except
251253 writeln(JSExceptValue);
252254 end;
253255end;
254256
255257begin
256258 DisplayContent;
257259end.
258260```
259261
260262# Navigation
261263
262264- [[pas2js|Pas2js]]
263265
264266---
265267*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).*
266268Category: Pas2js

Back to history