Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New principle: A Promise represents completion or a value, not a callback (#342) #496

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
53 changes: 53 additions & 0 deletions index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -1877,6 +1877,59 @@ 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}}.

<h3 id="allow-microtask-switches">A Promise represents completion or a value, not a callback</h3>

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.
But using `await` or similar wrappers queue a microtask:
<div class="example">
```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"
})();
```
</div>

In general, the settled result from an asynchronous function
should be usable at any time.

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
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.

<div class="example">
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 <em>after</em> the `getDisplayMedia()` promise. This allows
jan-ivar marked this conversation as resolved.
Show resolved Hide resolved
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.

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.
</div>

<h2 id="event-design">Event Design</h2>

<h3 id="one-time-events">Use promises for one time events</h3>
Expand Down