From 7905131005791757ec1990825dd6aea1994babc2 Mon Sep 17 00:00:00 2001 From: Jan-Ivar Bruaroey Date: Mon, 3 Jun 2024 15:40:41 -0400 Subject: [PATCH 01/10] New principle: A Promise represents a value, not a callback (#342) --- index.bs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/index.bs b/index.bs index a55b526..3d50724 100644 --- a/index.bs +++ b/index.bs @@ -1877,6 +1877,37 @@ If the bytes in the buffer have a natural intepretation as one of the other TypedArray types, provide that instead. For example, if the bytes represent Float32 values, use a {{Float32Array}}. +

A Promise represents a value, not a callback

+ +Avoid requiring that something needs to happen synchronously within +a promise resolution or rejection callback. + +Authors take for granted that an `async` function can be wrapped +in another that appends synchronous code at the end e.g. to inspect state +using try/catch. But the `await` queues a microtask: +
+```js +async function fooWrapper() { + const foo = await platform.foo(); + console.log("success"); + return foo; +} + +(async () => { + const foo = await fooWrapper(); + foo.bar(); // on the same task, but not the same microtask that logged "success" +})(); +``` +
+ +To not interfere with this pattern, the lowest recommended restriction is "same task". + +
+One case where this came up was the [captureController.setFocusBehavior()](https://w3c.github.io/mediacapture-screen-share/#dom-capturecontroller-setfocusbehavior) +method, where timing concerns were solved by adding an timeout on top of +the same task requirement. +
+

Event Design

Use promises for one time events

From 4eae825a18a33d2e066af99f876dc5e6b0918efe Mon Sep 17 00:00:00 2001 From: Jan-Ivar Bruaroey Date: Tue, 4 Jun 2024 18:49:48 -0400 Subject: [PATCH 02/10] Improve prose --- index.bs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/index.bs b/index.bs index 3d50724..d220cb2 100644 --- a/index.bs +++ b/index.bs @@ -1882,7 +1882,7 @@ For example, if the bytes represent Float32 values, use a {{Float32Array}}. Avoid requiring that something needs to happen synchronously within a promise resolution or rejection callback. -Authors take for granted that an `async` function can be wrapped +Authors tend to expect that any `async` function can be wrapped in another that appends synchronous code at the end e.g. to inspect state using try/catch. But the `await` queues a microtask:
@@ -1900,12 +1900,15 @@ async function fooWrapper() { ```
-To not interfere with this pattern, the lowest recommended restriction is "same task". +To support this pattern, a specification that wishes to restrict `bar()` to +only succeed if called immediately following a promise resolving can +minimally limit it to the same task, but not the same microtask.
One case where this came up was the [captureController.setFocusBehavior()](https://w3c.github.io/mediacapture-screen-share/#dom-capturecontroller-setfocusbehavior) -method, where timing concerns were solved by adding an timeout on top of -the same task requirement. +method, where timing concerns were solved by adding a timeout on top of +the requirement of it being called on the same task that resolves the +`getDisplayMedia()` promise.

Event Design

From 7b0a008e11241b045bf468c68087d87224844050 Mon Sep 17 00:00:00 2001 From: Jan-Ivar Bruaroey Date: Tue, 11 Jun 2024 11:49:23 -0400 Subject: [PATCH 03/10] This might be so that a return value can be examined... Co-authored-by: Martin Thomson --- index.bs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index d220cb2..05a6669 100644 --- a/index.bs +++ b/index.bs @@ -1883,8 +1883,9 @@ Avoid requiring that something needs to happen synchronously within a promise resolution or rejection callback. Authors tend to expect that any `async` function can be wrapped -in another that appends synchronous code at the end e.g. to inspect state -using try/catch. But the `await` queues a microtask: +in another that appends synchronous code at the end. +This might be so that a return value can be examined, such as using try/catch. +Using `await` or similar wrappers queues a microtask:
```js async function fooWrapper() { From 2ac6cfe9e743382c49efc26eab7ddf4f1ade9489 Mon Sep 17 00:00:00 2001 From: Jan-Ivar Bruaroey Date: Tue, 11 Jun 2024 14:04:36 -0400 Subject: [PATCH 04/10] Rephrase to discourage limitations on acting on a "settled result" --- index.bs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/index.bs b/index.bs index 05a6669..822a19c 100644 --- a/index.bs +++ b/index.bs @@ -1884,8 +1884,8 @@ a promise resolution or rejection callback. Authors tend to expect that any `async` function can be wrapped in another that appends synchronous code at the end. -This might be so that a return value can be examined, such as using try/catch. -Using `await` or similar wrappers queues a microtask: +This might be to examine a resolved value or rejection using try/catch. +But using `await` or similar wrappers queue a microtask:
```js async function fooWrapper() { @@ -1901,13 +1901,25 @@ async function fooWrapper() { ```
-To support this pattern, a specification that wishes to restrict `bar()` to -only succeed if called immediately following a promise resolving can -minimally limit it to the same task, but not the same microtask. +In general, the settled result from an asynchronous function +should be usable at any time. + +However, there could be cases where APIs might need to restrict +when a result can be acted on. + +In the above example, perhaps the `platform.foo()` function +establishes state that has real-time dependencies such that calling +`bar()` is not viable at any future time. + +Never limit the viability of acting on a settled result to a microtask, +as this prevents most forms of code composition. + +If possible, set a short timeout interval. Failing that, viability can +be limited to the same task, but not a single microtask.
One case where this came up was the [captureController.setFocusBehavior()](https://w3c.github.io/mediacapture-screen-share/#dom-capturecontroller-setfocusbehavior) -method, where timing concerns were solved by adding a timeout on top of +method, where timing concerns were instead solved by adding a timeout on top of the requirement of it being called on the same task that resolves the `getDisplayMedia()` promise.
From 2825cbd24b3a63e2d71bcd0d53745d85ca6af549 Mon Sep 17 00:00:00 2001 From: Jan-Ivar Bruaroey Date: Wed, 17 Jul 2024 11:45:51 -0400 Subject: [PATCH 05/10] s/note/example Co-authored-by: Lea Verou --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 822a19c..4e97bad 100644 --- a/index.bs +++ b/index.bs @@ -1917,7 +1917,7 @@ as this prevents most forms of code composition. If possible, set a short timeout interval. Failing that, viability can be limited to the same task, but not a single microtask. -
+
One case where this came up was the [captureController.setFocusBehavior()](https://w3c.github.io/mediacapture-screen-share/#dom-capturecontroller-setfocusbehavior) method, where timing concerns were instead solved by adding a timeout on top of the requirement of it being called on the same task that resolves the From 15258a4f150ff25748baceb7b7643ac4ae46bbf5 Mon Sep 17 00:00:00 2001 From: Jan-Ivar Bruaroey Date: Wed, 17 Jul 2024 12:44:46 -0400 Subject: [PATCH 06/10] new title and more work on examples --- index.bs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/index.bs b/index.bs index 4e97bad..f4ef23c 100644 --- a/index.bs +++ b/index.bs @@ -1877,11 +1877,12 @@ If the bytes in the buffer have a natural intepretation as one of the other TypedArray types, provide that instead. For example, if the bytes represent Float32 values, use a {{Float32Array}}. -

A Promise represents a value, not a callback

+

A Promise represents completion or a value, not a callback

Avoid requiring that something needs to happen synchronously within a promise resolution or rejection callback. +This breaks basic assumptions around asynchronous APIs being wrappable. Authors tend to expect that any `async` function can be wrapped in another that appends synchronous code at the end. This might be to examine a resolved value or rejection using try/catch. @@ -1904,8 +1905,8 @@ async function fooWrapper() { In general, the settled result from an asynchronous function should be usable at any time. -However, there could be cases where APIs might need to restrict -when a result can be acted on. +However, if the result of your API is time sensitive, you might need +to restrict when it can be acted on. In the above example, perhaps the `platform.foo()` function establishes state that has real-time dependencies such that calling @@ -1919,9 +1920,14 @@ be limited to the same task, but not a single microtask.
One case where this came up was the [captureController.setFocusBehavior()](https://w3c.github.io/mediacapture-screen-share/#dom-capturecontroller-setfocusbehavior) -method, where timing concerns were instead solved by adding a timeout on top of -the requirement of it being called on the same task that resolves the -`getDisplayMedia()` promise. +method which controls whether a window the user selects to screen-capture +should immediately be pushed to the front or not. It can be called as late +as right after the `getDisplayMedia()` promise. This allows +applications to make a different decision based on the window the user chose. +But the application must act right away or this becomes surprising to the user. + +This solution was to add a timeout on top of the requirement of it being +called on the same task that resolves the `getDisplayMedia()` promise.

Event Design

From da095298abca269147a6644319ad186db4cd8a7c Mon Sep 17 00:00:00 2001 From: Jan-Ivar Bruaroey Date: Wed, 17 Jul 2024 12:52:12 -0400 Subject: [PATCH 07/10] typo --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index f4ef23c..2ec7461 100644 --- a/index.bs +++ b/index.bs @@ -1926,7 +1926,7 @@ as right after the `getDisplayMedia()` promise. This allows applications to make a different decision based on the window the user chose. But the application must act right away or this becomes surprising to the user. -This solution was to add a timeout on top of the requirement of it being +The solution was to add a timeout on top of the requirement of it being called on the same task that resolves the `getDisplayMedia()` promise.
From c6655479431a26f9ce6ede691d847f7e5271f71b Mon Sep 17 00:00:00 2001 From: Jan-Ivar Bruaroey Date: Wed, 17 Jul 2024 12:54:59 -0400 Subject: [PATCH 08/10] s/time sensitive/timing sensitive/ --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 2ec7461..e625843 100644 --- a/index.bs +++ b/index.bs @@ -1905,7 +1905,7 @@ async function fooWrapper() { In general, the settled result from an asynchronous function should be usable at any time. -However, if the result of your API is time sensitive, you might need +However, if the result of your API is timing sensitive, you might need to restrict when it can be acted on. In the above example, perhaps the `platform.foo()` function From a3d6a48930d783a9b9dd13b458c2fbbe29fd632d Mon Sep 17 00:00:00 2001 From: Jan-Ivar Bruaroey Date: Thu, 25 Jul 2024 20:23:51 -0400 Subject: [PATCH 09/10] ...resolution of the ... Co-authored-by: Harald Alvestrand --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index e625843..c31c8e7 100644 --- a/index.bs +++ b/index.bs @@ -1922,7 +1922,7 @@ be limited to the same task, but not a single microtask. One case where this came up was the [captureController.setFocusBehavior()](https://w3c.github.io/mediacapture-screen-share/#dom-capturecontroller-setfocusbehavior) method which controls whether a window the user selects to screen-capture should immediately be pushed to the front or not. It can be called as late -as right after the `getDisplayMedia()` promise. This allows +as right after the resolution of the `getDisplayMedia()` promise. This allows applications to make a different decision based on the window the user chose. But the application must act right away or this becomes surprising to the user. From 8f15631244fd8f89a983ec40f154a7c6a49a4535 Mon Sep 17 00:00:00 2001 From: Jan-Ivar Bruaroey Date: Mon, 5 Aug 2024 12:08:58 -0400 Subject: [PATCH 10/10] add sentence on when to use callbacks --- index.bs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/index.bs b/index.bs index c31c8e7..580e432 100644 --- a/index.bs +++ b/index.bs @@ -1930,6 +1930,9 @@ The solution was to add a timeout on top of the requirement of it being called on the same task that resolves the `getDisplayMedia()` promise.
+If an API depends on setting up temporary conditions then invoking the caller, +that is a good reason to use a callback rather than a promise. +

Event Design

Use promises for one time events