Writing Promise-Using Specifications

Finding of the W3C TAG,

Domenic Denicola (Google)
GitHub w3ctag/promises-guide (file an issue; open issues)


This document gives guidance on how to write specifications that create, accept, or manipulate promises. It also includes a series of prose shorthands you can use in your specification to work with promises.

1. Introduction

A promise is an object that represents the eventual result of a single asynchronous operation. They can be returned from asynchronous functions, thus allowing consumers to not only queue up callbacks to be called when the operation succeeds or fails, but also to manipulate the returned promise object, opening up a variety of possibilities.

Promises have been battle-tested in many JavaScript libraries, including as part of popular frameworks like Dojo, jQuery, YUI, Ember, Angular, WinJS, Q, and others. This culminated in the Promises/A+ community specification which most libraries conformed to. Now, a standard Promise class is included in the next version of ECMAScript [ECMASCRIPT], allowing web platform APIs to return promises for their asynchronous operations.

Promises are now the web platform’s paradigm for all "one and done" asynchronous operations. Previously, specifications used a variety of mismatched mechanisms for such operations. Going forward, all asynchronous operations of this type should be specified to instead return promises, giving our platform a unified primitive for asynchronicity.

Although the primary audience of this document is specification writers, it is also useful for JavaScript developers who want to write libraries or applications using promises. We will call out sections that are spec-writer specific so developers can skip them more easily.

2. When to Use Promises

2.1. One-and-Done Operations

The primary use case for promises is returning them from a method that kicks off a single asynchronous operation. One should think of promise-returning functions as asynchronous functions, in contrast to normal synchronous functions; there is a very strong analogy here, and keeping it in mind makes such functions easier to write and reason about.

For example, normal synchronous functions can either return a value or throw an exception. Asynchronous functions will, analogously, return a promise, which can either be fulfilled with a value, or rejected with a reason. Just like a synchronous function that returns "nothing" (i.e. undefined), promises returned by asynchronous functions can be fulfilled with nothing (undefined); in this case the promise fulfillment simply signals completion of the asynchronous operation.

Examples of such asynchronous operations abound throughout web specifications:

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.

2.2. One-Time "Events"

Because promises can be subscribed to even after they’ve already been fulfilled or rejected, they can be very useful for a certain class of "event." When something only happens once, and authors often want to observe the status of it after it’s already occurred, providing a promise that becomes fulfilled when that eventuality comes to pass gives a very convenient API.

The prototypical example of such an "event" is a loaded indicator: a resource such as an image, font, or even document, could provide a loaded property that is a promise that becomes fulfilled only when the resource has fully loaded (or becomes rejected if there’s an error loading the resource). Then, authors can always queue up actions to be executed once the resource is ready by doing resource.loaded.then(onLoaded, onFailure). This will work even if the resource was loaded already, queueing a microtask to execute onLoaded. This is in contrast to an event model, where if the author is not subscribed at the time the event fires, that information is lost.

2.3. More General State Transitions

In certain cases, promises can be useful as a general mechanism for signaling state transitions. This usage is subtle, but can provide a very nice API for consumers when done correctly.

One can think of this pattern as a generalization of the one-time "events" use case. For example, take <img> elements. By resetting their src property, they can be re-loaded; that is, they can transition back from a loaded state to an unloaded state. Thus becoming loaded is not a one-time occasion: instead, the image actually consists of a state machine that moves back and forth between loaded and unloaded states. In such a scenario, it is still useful to give images a promise-returning loaded property, which will signal the next state transition to a loaded state (or be already fulfilled if the image is already in a loaded state). This property should return the same promise every time it is retrieved, until the image moves backward from the loaded state into the unloaded state. Once that occurs, a new promise is created, representing the next transition to loaded.

There are many places in the platform where this can be useful, not only for resources which can transition to loaded, but e.g. for animations that can transition to finished, or expensive resources that can transition to disposed, or caches that can become unloaded.

A slight variant of this pattern occurs when your class contains a method that causes a state transition, and you want to indicate when that state transition completes. In that case you can return a promise from the method, instead of keeping it as a property on your object. The streams API uses this variant for its wait() and close() methods. In general, methods should be used for actions, and properties for informational state transitions.

To close, we must caution against over-using this pattern. Not every state transition needs a corresponding promise-property. Indicators that it might be useful include:

3. When Not to Use Promises

Although promises are widely applicable to asynchronous operations of many sorts, there are still situations where they are not appropriate, even for asynchronicity.

3.1. Recurring Events

Any event that can occur more than once is not a good candidate for the "one and done" model of promises. There is no single asynchronous operation for the promise to represent, but instead a series of events. Conventional EventTarget usage is just fine here.

3.2. Streaming Data

If the amount of data involved is potentially large, and could be produced incrementally, promises are probably not the right solution. Instead, you’ll want to use the under-development streams API, which allows authors to process and compose data streams incrementally, without buffering the entire contents of the stream into memory.

Note that in some cases, you could provide a promise API alongside a streaming API, as a convenience for those cases when buffering all the data into memory is not a concern. But this would be a supporting, not primary, role.

4. API Design Guidance

There are a few subtle aspects of using or accepting promises in your API. Here we attempt to address commonly-encountered questions and situations.

4.1. Errors

4.1.1. Promise-Returning Functions Should Always Return Promises

Promise-returning functions should always return a promise, under all circumstances. Even if the result is available synchronously, or the inputs can be detected as invalid synchronously, this information needs to be communicated through a uniform channel so that a developer can be sure that by doing


they are handling all successes and all errors.

In particular, promise-returning functions should never synchronously throw errors, since that would force duplicate error-handling logic on the consumer: once in a catch (e) { ... } block, and once in a .catch(e => { ... }) block. Even argument validation errors are not OK. Instead, all errors should be signaled by returning rejected promises.

For WebIDL-based specs, this is taken care of automatically by the WebIDL specification: any exceptions thrown by WebIDL operations, or by the WebIDL overload resolution algorithm itself, are automatically converted into rejections. For an example of how to do manual validation, see the validatedDelay example below.

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.

4.1.3. Rejections Should Be Used for Exceptional Situations

What exactly you consider "exceptional" is up for debate, as always. But, you should always ask, before rejecting a promise: if this function was synchronous, would I expect a thrown exception under this circumstance? Or perhaps a failure value (like null, false, or undefined)? You should think about which behavior is more useful for consumers of your API. If you’re not sure, pretend your API is synchronous and then think if your developers would expect a thrown exception.

Good cases for rejections include:

Bad uses of rejections include:

Cases where a judgement call will be necessary include:

4.2. Asynchronous Algorithms

This section is primarily for spec writers, dealing with the vagaries of clearly manifesting asynchronous algorithm flow in prose. For more background on this subject, see Anne’s blog post on asynchronicity.

4.2.1. Simply Resolve or Reject the Promise

Unlike in the old world of callbacks, there’s no need to create separate callback types (e.g. in WebIDL) for your success and error cases. Instead, just resolve or reject your promise.

4.2.2. Note Parallel Steps Explicitly

It is important to note which steps in your algorithms will be run in parallel with the author’s JavaScript code, i.e. without blocking script execution. This instructs implementers as to which operations will need to use e.g. a background thread or asychronous I/O calls. And it helps authors to know the expected sequencing of their operations with respect to those of your algorithm. To do this, use the phrase in parallel from [HTML].

As an example, the following steps will give a promise that is resolved after ms milliseconds:

  1. Let p be a new promise.

  2. Run the following steps in parallel:

    1. Wait ms milliseconds.

    2. Resolve p with undefined.

  3. Return p.

If we had omitted the "Run the following steps in parallel" heading, then the algorithm would have instructed implementers to block the main thread for ms milliseconds, which is very bad! Whereas as written, this algorithm correctly describes a non-blocking wait.

4.2.3. Queue Tasks to Invoke Developer Code

Promises abstract away many of the details regarding notifying the developer about async operations. For example, you can say "resolve p with x" instead of e.g. "queue a task to call the callback cb with x," and it’s understood that this will use the normal promise mechanisms. (Namely, the developer can wait for fulfillment or rejection by passing callbacks to the promise’s then method, which will call those callbacks in the next microtask.) So in most cases, you will not need to explicitly queue tasks inside your promise-based asynchronous algorithms.

However, in cases where you need to interface with developer code in more ways than can be mediated via the promise, you’ll still need to queue a task. For example, you may want to fire an event, which can call into developer event handlers. Or you may need to perform a structured clone operation, which can trigger getters. If these things must be done inside the asynchronous portion of your algorithm, you need to specify that they are done via a queued task, and with a specific task queue. This nails down the exact time such developer-observable operations happen both in relation to other queued tasks, and to the microtask queue used by promises.

As an example, the following steps will return a promise resolved after ms milliseconds, but also fire an event named timerfinished on window:

  1. Let p be a new promise.

  2. Run the following steps in parallel:

    1. Wait ms milliseconds.

    2. Resolve p with undefined.

    3. Queue a task to fire a simple event named timerfinished at the browsing context active document’s Window object.

  3. Return p.

4.3. Accepting Promises

4.3.1. Promise Arguments Should Be Resolved

In general, when an argument is expected to be a promise, you should also allow thenables and non-promise values by resolving the argument to a promise before using it. You should never do a type-detection on the incoming value, or overload between promises and other values, or put promises in a union type.

In WebIDL-using specs, this is automatically taken care of by the WebIDL promise type. To see what it means in JavaScript, consider the following function, which adds a delay of ms milliseconds to a promise:

function addDelay(promise, ms) {
    return Promise.resolve(promise).then(v =>
        new Promise(resolve =>
            setTimeout(() => resolve(v), ms);

var p1 = addDelay(doAsyncOperation(), 500);
var p2 = addDelay("value", 1000);

In this example, p1 will be fulfilled 500 ms after the promise returned by doAsyncOperation() fulfills, with that operation’s value. (Or p1 will reject as soon as that promise rejects.) And, since we resolve the incoming argument to a promise, the function can also work when you pass it the string "value": p2 will be fulfilled with "value" after 1000 ms. In this way, we essentially treat it as an immediately-fulfilled promise for that value.

4.3.2. Developer-Supplied Promise-Returning Functions Should Be "Promise-Called"

If the developer supplies you with a function that you expect to return a promise, you should also allow it to return a thenable or non-promise value, or even throw an exception, and treat all these cases as if they had returned an analogous promise. We can encapsulate this process in an operation called promise-calling the supplied function. This allows us to have the same reaction to synchronous forms of success and failure that we would to asynchronous forms.

See the resource.open example below for further discussion of how and why this should be used.

5. Shorthand Phrases

This section is primarily for spec writers, dealing with ways of performing common promise operations in prose.
This section should eventually move to WebIDL, where it belongs. See #27.

When writing such specifications, it’s convenient to be able to refer to common promise operations concisely. We define here a set of shorthands that allow you to do so.

5.1. Creating Promises

"A new promise#a-new-promiseReferenced in:4.2.2. Note Parallel Steps Explicitly4.2.3. Queue Tasks to Invoke Developer Code6.1. delay(ms)6.2. validatedDelay(ms)6.3. addDelay(promise, ms)6.5. environment.ready6.6. addBookmark()" gives a new, initialized-but-unresolved promise object to manipulate further. It is equivalent to calling new Promise((resolve, reject) => { ... }), using the initial value of the Promise constructor. Here ... stands in for code that saves the value of resolve and reject for later use by the shorthands under §5.2 Manipulating Promises.

"A promise resolved with#a-promise-resolved-withReferenced in:5.6. A Note on Realms x" or "x resolved as a promise#resolved-as-a-promiseReferenced in:5.5. Promise-Calling" is shorthand for the result of Promise.resolve(x), using the initial value of Promise.resolve.

"A promise rejected with#a-promise-rejected-withReferenced in:5.5. Promise-Calling6.2. validatedDelay(ms) (2)6.6. addBookmark() (2)6.7. batchRequest(urls)7.2. Promise<T> Parameters r" is shorthand for the result of Promise.reject(r), using the initial value of Promise.reject.

5.2. Manipulating Promises

"Resolve#resolve-promiseReferenced in:4.2.2. Note Parallel Steps Explicitly4.2.3. Queue Tasks to Invoke Developer Code (2)6.1. delay(ms)6.2. validatedDelay(ms)6.3. addDelay(promise, ms)6.5. environment.ready6.6. addBookmark() p with x" is shorthand for calling a previously-stored resolve function from creating p, with argument x.

"Reject#reject-promiseReferenced in:6.3. addDelay(promise, ms)6.5. environment.ready6.6. addBookmark() p with r" is shorthand for calling a previously-stored reject function from creating p, with argument r.

If the algorithm using these shorthands is running in parallel, the shorthands queue a task on p’s relevant settings object’s responsible event loop to call the stored function.

5.3. Reacting to Promises

"Upon fulfillment#upon-fulfillmentReferenced in:6.3. addDelay(promise, ms)7.2. Promise<T> Parameters of p with value v" is shorthand for calling p.then(onFulfilled), with the successive nested steps comprising the onFulfilled function, and using the initial value of Promise.prototype.then. The steps then have access to onFulfilled’s argument as v.

"Upon rejection#upon-rejectionReferenced in:6.3. addDelay(promise, ms)7.2. Promise<T> Parameters of p with reason r" is shorthand for calling p.then(undefined, onRejected), with the successive nested steps comprising the onRejected function, and using the initial value of Promise.prototype.then. The steps then have access to onRejected’s argument as r.

"Transforming#transforming-byReferenced in:5.3. Reacting to Promises (2)6.4. resource.open(resourcePath, openingOperation) p with a fulfillment and/or rejection handler" is shorthand for calling p.then(fulfillmentHandler, rejectionHandler), using the initial value of Promise.prototype.then.

Some examples of the latter phrase would be
  1. Return the result of transforming p with a fulfillment handler that returns undefined.


  1. Return the result of transforming p with a fulfillment handler that returns twice its first argument.

These correspond to

return p.then(() => undefined);


return p.then(x => 2 * x);

respectively. (More complicated transforms are of course possible as well, as shown in the resource.open example below.)

5.4. Aggregating Promises

The result of waiting for all#waiting-for-allReferenced in:6.7. batchRequest(urls) (2) 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.

This phrase is useful when you wish to perform multiple asynchronous operations in parallel that return promises, and then react to them all together. If all of the given promises fulfill, then the resulting promise will be fulfilled with an array corresponding to the fulfillment values. If any of them reject, then the resulting promise will be rejected with the first rejection reason to occur.

An example usage of this phrase is found in §6.7 batchRequest(urls).

5.5. Promise-Calling

The result of promise-calling#promise-callingReferenced in:4.3.2. Developer-Supplied Promise-Returning Functions Should Be "Promise-Called"6.4. resource.open(resourcePath, openingOperation) (2) f(...args) is:

In JavaScript, you might express promise-calling this way:
function promiseCall(f, ...args) {
    try {
        return Promise.resolve(f(...args));
    } catch (e) {
        return Promise.reject(e);

5.6. A Note on Realms

In all cases, when we use phrases like "the initial value of Promise.resolve," we mean the initial value within the realm associated to the function being specified. So for example, if window.f() is specified to return "a promise resolved with 1," then:

assert(windowA.f().constructor === windowA.Promise);
assert(windowB.f().constructor === windowB.Promise);

// The realm of the object the function is being invoked on has no effect;
// the function’s realm is all that matters.
assert(windowA.f.call(windowB).constructor === windowA.Promise);

// This means mutations to the Promise constructor also propagate.
windowA.Promise.prototype.foo = "bar";
assert(windowA.f().foo === "bar");
assert(windowB.f().foo === undefined);

// But since the algorithm for Promise.resolve uses the un-modifiable %Promise%
// intrinsic, instead of consulting the global, modifying the global property named
// "Promise" does not impact the return value.
const oldPromise = windowA.Promise;
windowA.Promise = () => throw new Error("I break developer code, but not platform code!");
assert(windowA.f().constructor !== windowA.Promise);
assert(windowA.f().constructor === oldPromise);

For more information, see this www-tag thread, especially the replies.

6. Examples

This section is mostly for spec writers, although it does give examples of the spec prose translated into JavaScript.

6.1. delay(ms)

delay is a function that returns a promise that will be fulfilled in ms milliseconds. It illustrates how simply you can resolve a promise, with one line of prose.

  1. Let ms be ToNumber(ms).

  2. If ms is NaN, let ms be +0; otherwise let ms be the maximum of ms and +0.

  3. Let p be a new promise.

  4. Run the following steps in parallel:

    1. Wait ms milliseconds.

    2. Resolve p with undefined.

  5. Return p.

The equivalent function in JavaScript would be

function delay(ms) {
    ms = Number(ms);
    ms = Number.isNaN(ms) ? +0 : Math.max(ms, +0);
    return new Promise(resolve => setTimeout(resolve, ms));

or, in a more one-to-one correspondence with the specified steps,

function delay(ms) {
    // Steps 1, 2
    ms = Number(ms);
    ms = Number.isNaN(ms) ? +0 : Math.max(ms, +0);

    // Step 3
    let resolve;
    const p = new Promise(r => { resolve = r });

    // Step 4
    setTimeout(() => resolve(undefined), ms);

    // Step 5
    return p;

6.2. validatedDelay(ms)

The validatedDelay function is much like the delay function, except it will validate its arguments. This shows how to use rejected promises to signal immediate failure before even starting any asynchronous operations.

  1. Let ms be ToNumber(ms).

  2. If ms is NaN, return a promise rejected with a TypeError.

  3. If ms is less than zero, return a promise rejected with a RangeError.

  4. Let p be a new promise.

  5. Run the following steps in parallel:

    1. Wait ms milliseconds.

    2. Resolve p with undefined.

  6. Return p.

The equivalent function in JavaScript would be

function delay(ms) {
    ms = Number(ms);

    if (Number.isNaN(ms)) {
        return Promise.reject(new TypeError("Not a number."));
    if (ms < 0) {
        return Promise.reject(new RangeError("ms must be at least zero."));

    return new Promise(resolve => setTimeout(resolve, ms));

6.3. addDelay(promise, ms)

addDelay is a function that adds an extra ms milliseconds of delay between promise settling and the returned promise settling. Notice how it resolves the incoming argument to a promise, so that you could pass it a non-promise value or a thenable.

  1. Let ms be ToNumber(ms).

  2. If ms is NaN, let ms be +0; otherwise let ms be the maximum of ms and +0.

  3. Let p be a new promise.

  4. Let resolvedToPromise be the result of resolving promise to a promise.

  5. Upon fulfillment of resolvedToPromise with value v, perform the following steps in parallel:

    1. Wait ms milliseconds.

    2. Resolve p with v.

  6. Upon rejection of resolvedToPromise with reason r, perform the following steps in parallel:

    1. Wait ms milliseconds.

    2. Reject p with r.

  7. Return p.

The equivalent function in JavaScript would be

function addDelay(promise, ms) {
    ms = Number(ms);
    ms = Number.isNaN(ms) ? +0 : Math.max(ms, +0);

    let resolve, reject;
    const p = new Promise((r, rr) => { resolve = r; reject = rr; });

    const resolvedToPromise = Promise.resolve(promise);
        v => setTimeout(() => resolve(v), ms),
        r => setTimeout(() => reject(r), ms)

    return p;

6.4. resource.open(resourcePath, openingOperation)

resource.open is a method that executes the passed function openingOperation to do most of its work, but then updates the resource’s properties to reflect the result of this operation. It is a simplified version of some of the techniques used in the streams specification. The method is meant to illustrate how and why you might promise-call another function.

  1. Let resourcePath be ToString(resourcePath).

  2. Let openingPromise be the result of promise-calling openingOperation(resourcePath).

  3. Return the result of transforming openingPromise with:

    1. A fulfillment handler that sets this.status to "opened".

    2. A rejection handler that, when called with argument r, set this.status to "errored" and this.error to r.

The equivalent function in JavaScript would be

resource.open = function (resourcePath, openingOperation) {
    resourcePath = String(resourcePath);

    return promiseCall(openingOperation, resourcePath).then(
        v => {
            this.status = "opened";
        r => {
            this.status = "errored";
            this.error = r;

using the promiseCall function defined above.

Note how if we had instead just called openingOperation, i.e. done openingOperation(resourcePath) directly, then code like

would fail. It would not return a promise, so calling then on the return value would fail. Even if we accounted for that, what if synchronouslyOpenTheResource threw an error? We would want that to result in an "errored" status, but without promise-calling, that would not be the case: the error would simply cause resource.open to exit. So you can see that promise-calling is quite helpful here.

6.5. environment.ready

environment.ready is a property that signals when some part of some environment becomes "ready," e.g. a DOM document. It illustrates how to encode environmental asynchronicity.

Let every Environment object have a [[ready]] internal slot, initialized with a new promise. When the getter for environment.ready is called, return the value of this Environment object’s [[ready]] internal slot.

The following steps might be inserted toward the end of some algorithm for readying Environment objects:

  1. If the environment becomes ready successfully, resolve this Environment object’s [[ready]] promise with undefined.

  2. If the environment fails to become ready, reject this Environment object’s [[ready]] promise with an Error instance explaining the load failure.

6.6. addBookmark()

addBookmark is a function that requests that the user add the current web page as a bookmark. It’s drawn from some iterative design work and illustrates a more real-world scenario of appealing to environmental asynchrony, as well as immediate rejections.

  1. If this method was not invoked as a result of explicit user action, return a promise rejected with a new DOMException whose name is "SecurityError".

  2. If the document’s mode of operation is standalone, return a promise rejected with a new DOMException whose name is "NotSupported".

  3. Let promise be a new promise.

  4. Let info be the result of getting a web application’s metadata.

  5. Run the following steps in parallel:

    1. Using info, and in a manner that is user-agent specific, allow the end user to make a choice as to whether they want to add the bookmark.

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

      2. Otherwise, resolve promise with undefined.

  6. Return promise.

6.7. batchRequest(urls)

Several places in [SERVICE-WORKERS] use waiting for all. batchRequest illustrates a simplified version of one of their uses. It takes as input an iterable of URLs, and returns a promise for an array of Response objects created by fetching the corresponding URL. If any of the fetches fail, it will return a promise rejected with that failure.

  1. Let responsePromises be a new empty list

  2. For each value url of urls,

    1. Let url be the result of converting url to a USVString.

    2. Let req be the result of creating a new Request passing url to the constructor.

    3. Let p be the result of calling fetch with req.

    4. Add p to responsePromises.

  3. Return the result of waiting for all of responsePromises.

7. WebIDL and Promises

This section is primarily for spec writers, dealing with how promises integrate with an interface definition language often used in web specs.

[WEBIDL] provides a Promise<T> type which can be used when writing specifications that expose their API through WebIDL. We summarize the impact of Promise<T> here for easy reference.

7.1. Promise<T> Return Values

Like all WebIDL return values, declaring a return value of type Promise<T> has no impact on the algorithm’s actual return steps. It is simply a form of documentation, and if you return something that is not a promise or is a promise with a fulfillment value that is not of WebIDL-type T, then you have written incorrect documentation into your spec.

However, declaring that your method or accessor returns a promise does have one important impact: it ensures that any exceptions that it would otherwise throw, e.g. as a result of failed type conversions, are caught and turned into rejected promises. (See the "Operations" section, "If O has a return type that is a promise type …", and similar phrases scattered throughout the document.) This automatically takes care of the advice in §4.1.1 Promise-Returning Functions Should Always Return Promises, at least for exceptions.

7.2. Promise<T> Parameters

When a parameter of a WebIDL method is declared as Promise<T>, it will automatically resolve any arguments passed in that position. This will take care of the "Promise Arguments Should Be Resolved" advice above.

If you have a WebIDL Promise<T> argument, you can use the WebIDL perform some steps once a promise is settled algorithm. This is much like our upon fulfillment … and upon rejection … shorthand phrases above, but it will add an additional step of converting the promise’s fulfillment value to the WebIDL type T before running any upon-fulfillment steps. Additionally it causes your algorithm to return a promise derived from running those steps. If the type conversion fails, your algorithm will return a promise rejected with the error causing that failure.

Note that the T here refers to a WebIDL type for the fulfillment value. Furthermore, it only has impact if you use the WebIDL "perform some steps …" algorithm, and not if you use the promise in other ways (such as passing it along to another function). If that is not relevant, we advise using Promise<any> for parameters.

As a consequence of the resolution behavior, Promise<T> parameters cannot be overloaded with any other parameters. For example, you cannot do:
void f(Promise<DOMString> x);
void f(DOMString y);

7.3. Developer Functions Returning Promises

In WebIDL, you consume JavaScript functions by declaring them as WebIDL callback functions (or, in rare cases, via callback interfaces) and later invoking them with a list of WebIDL values.

If you use WebIDL’s mechanisms for calling JavaScript functions, the invocation algorithm will automatically resolve return values and convert thrown exceptions into rejected promises. This automatically takes care of the advice in §4.3.2 Developer-Supplied Promise-Returning Functions Should Be "Promise-Called".

7.4. Examples

Promise-returning methods:
interface ProtectedResource {
  Promise<void> requestAccess();
  // ...

interface Quoter {
  Promise<DOMString> getInterestingQuote();
Promise-returning properties
interface StateMachine {
  readonly attribute Promise<void> loaded;

  Promise<void> load();
Promise-accepting methods
interface Waiter {
  void waitUntil(Promise<any> promise);
Promise-returning developer functions
callback Promise<DOMString> ResourceLoader();

interface ResourceConsumer {
  void loadAndConsumeResource(ResourceLoader loader);

Appendix: Legacy APIs for Asynchronicity

Many web platform APIs were written before the advent of promises, and thus came up with their own ad-hoc ways of signaling asynchronous operation completion or failure. These include:

If you find yourself doing something even remotely similar to these, stop, and instead use promises.


Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.


Terms defined by this specification

Terms defined by reference


Normative References

ECMAScript Language Specification. URL: https://tc39.github.io/ecma262/
Ian Hickson. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119
Cameron McCormack; Boris Zbarsky. WebIDL Level 1. 8 March 2016. CR. URL: https://heycam.github.io/webidl/

Informative References

Alex Russell; Jungkee Song; Jake Archibald. Service Workers. 25 June 2015. WD. URL: https://slightlyoff.github.io/ServiceWorker/spec/service_worker/