Git Product home page Git Product logo

promises-guide's Introduction

promises-guide's People

Contributors

annevk avatar dbaron avatar domenic avatar gildas-lormeau avatar hober avatar isonmad avatar jyasskin avatar marcoscaceres avatar mattiasbuelens avatar phistuck avatar slightlyoff avatar xfq avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

promises-guide's Issues

Cases when promises should *not* be used

From @twirl's email feedback:

Cases when promises should NOT be used (for example, using promises instead of events or for passing large amounts of data).


This is definitely an important point to emphasize, I agree. In particular I like phrasing it in terms of "one and done" async function calls versus e.g. recurring events or streams of data.

WebIDL usage example

The WebIDL spec is not very clear on the usage of the Promise<t> type. After reading the WebIDL spec, I had assumed that I could express both the resolve type and the error type:

//this is wrong
Promise<(undefined or DOMException)> requestBookmark ();

It would be nice if this doc contained some IDL examples too. And pointed out that there is no way to express what the reject type is going to be in WebIDL. You have to do it in Prose.

"A promise resolved with" can unexpectedly run JavaScript

https://www.w3.org/2001/tag/doc/promises-guide#shorthand-creating says:

"A promise resolved with x" or "x resolved as a promise" is shorthand for the result of Promise.resolve(x), using the initial value of Promise.resolve.

If x is a promise this can cause JavaScript to run, due to the definition of PromiseResolve in ECMASCRIPT, step 2a. Specifically, it calls Get(x, "constructor"). If Promise.prototype.constructor has been set on the global object, it will be consulted. If a getter has been set, it will be executed.

This is a problem for the CreateReadableStream() operation in the streams standard. It has a note that "CreateReadableStream throws an exception if and only if the supplied startAlgorithm throws.". This will not be true if startAlgorithm returns a Promise and the global Promise.prototype.constructor has been messed with, which is surprising.

It is not clear whether this should be fixed here or in the streams standard.

Rethink async algorithm advice and examples

Various parties, including @annevk, @bzbarsky, @tyoshino, and @tabatkins, had good feedback that some of my guidance around writing async algorithms was confusing, or misleading, or inaccurate.

The essential problems were:

  • I was implicitly assuming async-ness of any operation that waits, instead of making it explicit. For example, the most straightforward reading of the delay example would have it block for ms milliseconds.
  • Some people like the "return but continue running these steps asynchronously" style. I maintain it's mind-boggling nonsensical in its nonlinearity, but this guidance document might not be the place to kill it.
  • I need to pay more attention to what it means to "queue a task," exactly. My conception was that "queue a task to do the following" was a way of introducing an artificial delay. But it also plays a role in discussing how values get back to the JavaScript event loop from the outside world of C++: the C++ code needs to queue up a message to pass back to the JS thread. The "do not queue needless tasks" guidance didn't properly internalize this model and probably isn't very good as a result.
  • @tyoshino had some intriguing ideas on annotating algorithm steps, to allow for concise specification of which invariants must hold in terms of sync or asyncness of a given step. I tentatively like them, but again, that might be too ambitious for this document.

I am not entirely sure what the best way to resolve this is, but it's clear the current advice is not on target and I'm tempted to take a conservative approach that mostly just rolls it back. Implementers and spec writers seem pretty happy with how async algorithms are written already, in which case there's no real need to change it just because I think it would be better.

Shorthand for parallel steps

Lots of specs have algorithms that http://www.w3.org/2001/tag/doc/promises-guide#explicit-async-steps suggests be written as:

  1. Let p be a new promise.
  2. Run the following steps in parallel:
    1. Resolve p with the meat of the algorithm
  3. Return p.

Some existing algorithms, like http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-encrypt, use the following pattern instead:

  1. Let p be a new promise.
  2. Return p and asynchronously perform the remaining steps.
  3. Resolve p with the meat of the algorithm.

A few existing algorithms, like https://html.spec.whatwg.org/multipage/webappapis.html#dom-createimagebitmap, shorten this further:

  1. Return a new Promise, but continue running these steps in parallel.
  2. Resolve the Promise with the meat of the algorithm.

The current recommendation requires 1-2 extra bullets for every algorithm and requires that the meat of the algorithm be indented one extra level, compared to the 2 alternatives.

Would it be possible for the TAG's promises guide to endorse "continue running these steps in parallel" and the practice of leaving the Promise unnamed when there's only 1 Promise in the algorithm?

The `addBookmark()` example violates this guide's own advice

The guide advises “Rejections Should Be Used for Exceptional Situations”.

However, the addBookmark() example suggests using promise rejections to signal that a user decided not to save a bookmark:

If the end-user aborts the request to add the bookmark (e.g., they hit escape, or press a "cancel" button), reject promise with a new DOMException whose name is "AbortError".

A user choosing "cancel" from a "Save/cancel" dialog in their browser is not an exceptional situation. A better implementation of this use-case would be that the promise was resolved in both cases and that the resolved value is a boolean indicating whether the user has accepted saving the bookmark or not.

The downside of this approach is that this example then stops being a demonstration of how to use rejected promises. I think an example for that should better follow this document's own guidelines.

Examples of old cruft need some tweaking

@tyoshino pointed out that XHR isn't the best example since it goes through multiple states.

@annevk had some ideas in IRC:

01:03:38 Domenic_: fullscreen is different, events are fired in other documents too
01:05:24 Domenic_: also see WebRTC, toBlob on canvas, geolocation (also has more complicated API promises do not work for), etc.

Separate out "for implementers" section?

Might not be necessary, but @bzbarsky's talk of implementing the promise shorthands in semi-automated ways in this post made me think that perhaps some sections of the document should be more formalized. For example, if we formalize the shorthands, including such minutiae as realms (#9), that probably isn't something most spec-writers will care about. A normative appendix explaining exactly how the shorthands work, and other such algorithmic aspects, might be a nice separation of concerns that allows the main document to stay lighthearted and easy to read.

Updating deploy script

I need to update the deploy script to be more secure. In order to do this, I need the ability to add a deploy key to the repository, via the repository settings. @plinss, would you be able to make me an admin for this repository, or find some time to collaborate on IRC or similar to add the deploy key yourself?

Shorthand after the guidance

From @darobin's feedback email:

I'd put the shorthand after the guidance. Without the context of the guidance it's a bit dry and blunt; I think it would stick better if it came later.


I agree; I noticed this issue myself when re-reading a while back.

Deal with realms?

@bzbarsky had some good feedback on specifying the realms in which various operations run:

One issue I just realized, as I was considering how to implement some of this stuff in automated ways. All the terms in the "Creating Promises"
are implicitly assuming operation in a particular realm. Same for the ones in "Aggregating Promises" and the definition of "Transforming p with onFulfilled and onRejected". Same thing for the "Promise Arguments Should Be Cast" guidance.

They need to either explicitly specify which realm is used somehow or all consumers (that is, specs using this terminology) need to do it.

This is particularly relevant when functions from one realm are invoked on a this object or arguments from another realm (and possible from multiple other realms); the algorithm defining the behavior of the function may need to think carefully about what realms are used for which operations in that situation...

As per the follow-ups in the thread, this might not be promise-specific, but it's possibly something this guide could handle in its shorthand definitions.

Document using promise-returning methods for state transitions

Streams uses them this way, and @sicking was proposing something similar for Service Worker.

  • It should be a third major use of promises, as a generalization of one-and-done events.
  • Such cases should always be expressed as methods, never as getters.
  • We can use the example of image.loaded(), pointing out that image.loaded doesn't work because you can "reload" an image by setting its src.
  • We should caution against overuse of this pattern, as it steps on the toes of recurring events a bit.
  • The major difference between this and recurring events is the recurring part: adding listeners to promise-vending state transition methods only gives you notification of the next state transition, whereas adding an event listener gives you notification each time the event is triggered. The former is often what you want for state machine code.
  • We should note that it's particularly useful because it allows post-facto registration of interest.

More exotic things should be examplified

I think most Editors get the simple premise (reject/resolve) of promises, but won't necessarily know when to make use of things like .race and .all. Spec examples would be good there... just so Editors consider those as options/tools to do useful things when designing APIs.

I guess this is related to #6

Formalize (somehow!?) "run asynchronously"

This is actually really important. Ties into issues around what "run to completion" means. Things like, what is an "asynchronously run" set of steps allowed to do? Only non-observable things, like queuing and task, or resolving a promise. This is actually a constant danger with potential data races around every corner.

We think that the style in this guide (as opposed to "return and continue running the following steps") is probably better for guiding toward the right path and avoiding those dangers. But note that it isn't intrinsically better.

guidance on what error to specify is used when a promise is rejected

The document currently says;

4.1.2. Rejection Reasons Should Be Errors

Promise rejection reasons should always be instances of the ECMAScript Error type, just like synchronously-thrown exceptions should always be instances of Error as well.

In particular, for DOM or other web platform specs, this means you should never use DOMError, but instead use DOMException, which per WebIDL extends Error. You can of course also use one of the built-in ECMAScript error types.

Looking at existing web APIs, it seems that the most common error to use is one of the built-in ECMAScript error types (e.g. TypeError). I've seen a few examples of DOMException. I've not (yet) found any APIs which define their own Error classes to use with rejection.

When defining an API, sometimes the existing ECMAScript error types include a good fit ( AbortError, NotSupportedError, InvalidStateError). Sometimes they don't or there's a need to distinguish that a promise was rejected for a specific reason.

Please can be document be revised to give some more guidance on what to do when one of the built-in ECMAScript error types is not right.

Links into the promsies spec

From @darobin's feedback email:

It would be useful to have links from the various promise concepts straight into the promises spec. That would help someone looking at this document for advice to double-check or dig into a specific aspect.

We can probably use the ES6 spec HTML version now instead of my repo, for extra officialness.

Uses of "resolving x as a promise" and so forth need to specify the global

For example, https://streams.spec.whatwg.org/#ws-constructor step 14 says:

the result of resolving startResult as a promise

and I assume they mean http://www.w3.org/2001/tag/doc/promises-guide#resolved-as-a-promise but it's not clear which global's initial value of Promise.resolve should be used. Plausible things are the current Realm's global or some canonical global associated with the object that's creating the Promise, depending on whether we want the exact promise to depend on where the function being called came from or on whether we want it to depend on where the object the function is manipulating came from...

Note that for IDL-defined APIs that return a non-NewObject Promise we probably want the global of this; for NewObject ones current Realm might make sense.

Waiting for promises might not work correctly if the page messes with its standard objects

The spec says:

The result of waiting for all of a collection of promises is a promise created by calling Promise.all(promiseArray), where promiseArray is that collection in array form and we use the initial value of Promise.all.

If the intent is that this actually work reliably, then this is not enough. The output of the initial value of Promise.all in this case will depend on the current values of at least the following: Promise[Symbol.species], %ArrayPrototype%[Symbol.iterator], %ArrayIteratorPrototype%.next (if no one has changed out the array iterator on you), Promise.resolve, Promise.prototype.then (if no one has changed out Promise.resolve on you).

Really, you probably just want a different algorithm here.

Provide some terms to block in parallel steps.

The terms in this guide are currently all aimed at execution in steps that block the main event loop, so they transform promises instead of waiting on them. However, lots of steps run in parallel, where blocking is acceptable. In those contexts, it would be nice to be able to simply wait for a promise to settle and then respond to the result. For some examples of this, search for "settles" in http://www.w3.org/TR/service-workers/.

"Rejections Should Be Used for Exceptional Situations"

So, I kinda get this one... but I'm still not 100% given what is currently written. I think a few more illustrative examples would be useful in this section. In particular, you talk about situations where equivalent to a null or false would be returned by a synchronous API, but then you don't really tease out the details as to whether a promise should be used. I know it's kinda vague - because, as you discuss in the document, it's a bit of an Editor's decision. Maybe show a "this is good" vs "this is bad" or something?

"newly-created"

Please make this "Let /p/ be a new promise." We use "new" like that all over and it seems fine.

Add an abstract

From Ashok's email feedback:

Can you add an abstract on what promises are, how they help, their place in the spec world, that sort of thing.

Most certainly!!

Add appendix documenting legacy patterns for asynchronous operations

In 2.1. One-and-Done Operations, the guide reads (emphasis mine)

Previously, web specifications used a large variety of differing patterns for asynchronous operations. We’ve documented these in an appendix below, so you can get an idea of what is now considered legacy. Now that we have promises as a platform primitive, such approaches are no longer necessary.

I don't believe such and appendix is included, and it would certainly be useful (e.g. analysis of benefits/tradeoffs).

"Transforming with/by a fulfilment handler" is confusing

The term Transforming p with a fulfillment and/or rejection handler sounds like it modifies p:

var p = someAsyncOperation();
var p2 = p.catch(() => {}); // has no effect on p

One might not often care about p at this point, in which case the wording seems forgiveable, but in w3c/webrtc-pc#222, p critically is used later, and I had to make this point in prose to not confuse people. The term is also not very descriptive of the action happening.

Instead, how about:

_Let_ p2 _be the result of appending to_ p _a fulfillment and/or rejection handler that runs the following steps:_

?

Move shorthand phrases to WebIDL

(Spinning this off from #10.)

WebIDL is generally where this kind of normative spec-writing tool belongs. They'd need to get a bit more formal. We'd keep informative summaries here, probably after moving them to the WebIDL section.

Are unresolved promises rejected prior to a window.unload event?

What is best practice regarding the handling of unresolved promises when a browsing context is closed (nav away, window close)? Should the promises be explicitly rejected, and if so, what Error object should be returned so the reason for the rejection can be determined? Or should the promises remain unresolved, with only a window.unload event generated? And if rejected, what is the timing of the rejection in relation to the unload event?

This relates to development of the Web NFC API: w3c/web-nfc#57

Shorthand for calling promise-returning functions and trapping exceptions

As discovered in whatwg/streams#78 there is a need for some kind of shorthand that calls functions that are expected to return promises, to handle cases like:

  • Returning non-thenables => wrap in a promise
  • Returning thenables => convert to real promises
  • Throwing an exception => trap and convert to rejection

Here is a quick sketch:

The result of "promise-calling f(args...)" is:

  1. If the call throws an exception e, a promise rejected with e.
  2. Otherwise, the return value of the call, cast to a promise.

I tried making this ECMAScript-ey formal but it seemed unnecessarily complicated given that these prose shorthands are inherently imprecise.


Examples will be important for this.

De-ES6ify (for now!?)

It may be too confusing for spec authors.

Alternately add a note saying we're using ES6, or in a non-Markdown version, have a little toggle between ES5/ES6.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.