Jsdoc: Feature: Documenting Promises

Created on 28 Oct 2013  ·  88Comments  ·  Source: jsdoc/jsdoc

Problem
All my projects now have functions that returns promises.

Idea
How about some specific promise documentation?

Example

/**
 * @promise Get user from database.
 * @resolve {object} user information
 * @reject {Error} validation error, connection error
 */

Following this spec: http://promises-aplus.github.io/promises-spec/

feature

Most helpful comment

In VS Code and TypeScript, it’s possible to use @return {Promise<T>}, where T is the type of the resolved value.

All 88 comments

I'm curious to hear from anyone else who has an opinion about the JSDoc syntax for documenting promises, or about what this should look like in the generated documentation.

In #429, @cowwoc suggested using the same syntax that we use for typedefs and callbacks (I've adapted it to refer to "promises" rather than "futures"):

/**
 * Some method.
 *
 * @method
 * @returns {Promise} A promise that returns {@link MyPromise~onResolve} if resolved
 * and {@link MyPromise~onReject} if rejected.
 */

At Medium, it was common to use a syntax similar to objects and arrays. Although we typically only documented the resolved case (not the error case), I imagine something like this might work:

/**
 * Some method.
 *
 * @returns {Promise.<string, Error>} A promise that returns a string if resolved,
 *     or an Error if rejected.
 */

What do you think?

I've been using a syntax that's basically identical to @returns

/**
 * @promise {Boolean} true if some condition is true
 */

If it's necessary to document the rejection error:

/**
 * @promise {Boolean} if some condition is true then true, otherwise false.
 * @rejects {Error} if some error occurs.
 */

I think it's pretty unambiguous, it'd be nice if JSDoc supported this. Don't really like the initial proposal of having 3 distinct tags, most of the time we just want one - @promise, which can be used in place of the @returns tag.

I'm not against the idea of 2 tags per-se but I dislike the names you chose. "A function @return X" sounds like proper English. "A function @rejects X" makes no sense. The function is not rejecting X. It's returning something that itself may return X on failure. That's not the same thing as the function itself doing so.

@cowoc I actually agree that @rejects is not ideal, do you have any other suggestions?

@phpnode but how would you document function's parameter, which is a promise? E.g.

/**
 * @param {??} p
 * @returns {??}
 */
function doSomething(p) {
  return p.then(() => { ... });
}

In this case, I really like @kylehg's solution, where Promise is yet another (generic) type

I'm not sure why we'd need to change anything for @param at all, if it's a promise it would look like:

/**
 * @param {Promise} p a promise.
 */

But it would be much clearer if one would have an ability to specify some details about this Promise

/**
 * @param {Promise.<String>} p my promise
 */
function doSomth(p) {
  p.then((str) => { str.substr(2) });
}

@narqo how is a promise different to any other object which may encapsulate a value? How do you indicate the contents of a collection object for instance?

According to JSDoc3, you can use @typedef tag for this.

For me, since Promises are going to become a part of DOM (or ECMAScript?) standard, they should be documented the same way we document other embedded non-primitive types, like Array or Object.

+1 @narqo

+1 @narqo

This should simply be generics. Promise is a type that is parameterized by another type. It is decades old, no need to invent things.

@kennknowles it's a common enough pattern that it warrants its own tag. I think a lot of the points here, especially regarding using @typedef, miss the point - that if you want people to use this, you have to make it easy for them. using a syntax like:

/**
 * @promise {Boolean} if foo then true
 */

is _easy_, unambiguous and covers 95% of what people need. It's also much nicer to read than:

/**
 * @return {Promise.<Boolean>} if foo then true
 */

Most people will not document the error case, but it could be something like:

/**
 * @promise {String} the name of the thing
 * @fail {Error} If something went wrong.
 */

Passing promises as function arguments is not a common pattern, so, don't optimise for it. In such a case, the promise is just an object like any other.

@phpnode Why not both? I'm completely fine with adding special tags to make common cases easy. But right now there's no underlying foundation to build the tag on, so I can't even do it the way you dislike. (It may not be pretty, but it has the benefit of being obviously correct and well understood.)

BTW I actually don't "want people to use this". I am a mere user of jsDoc, just casting my vote for an approach that would actually solve it for me.

For what it's worth, I'm in favor of some mechanism which encourages users to specify _both_ the success and failure value. For this reason, I believe @return and other tags meant to denote a single value are inappropriate.

The way I see it, Promises @return and @throws but asynchronously. I leave it up to you to decide how to represent that, but I will point out that being able to specify multiple @throws allows the documentation to scale in case different kind of errors are thrown.

@kennknowles sure, being able to do it the more verbose way as well would be great, using @promise is just sugar for the same thing. I think we all benefit from better documentation :)

@cowwoc multiple @fail tags could be supported, just like @throws:

/**
 * Make a request
 *
 * @promise {Object} the result of the request
 * @fail {ConnectionError} if the connection fails
 * @fail {RequestError} if the request is invalid
 * @fail {Error} if something else went wrong
 */

@phpnode I'm okay with the concept of using multiple tags but I'm not sure about the naming. Perhaps we should use names that mirror the terminology used in the Promises API: @resolves and @rejects similar to how @returns and @throws mirror the JS constructs return and throw.

I'm 100% for using @resolves and @rejects (multiple), as @cowwoc suggested to conform with the standard terminology. Anything else confuses and gives no advantage.

Thanks to all of you for contributing your ideas!

I have to say that I agree with @cowwoc's comment earlier in this discussion:

"A function @rejects X" makes no sense. The function is not rejecting X. It's returning something that itself may return X on failure. That's not the same thing as the function itself doing so.

Also, note that the draft ES6 spec uses the terms "fulfilled" and "rejected" to describe potential states of a promise. As the spec says, you can "resolve" a promise by rejecting it, or even by locking it into a "pending" state.

So far, the idea I like best is the suggestion from @kylehg, but with a type union in the generic:

/**
 * Retrieve the user's favorite color.
 *
 * @returns {Promise.<string|Error>} The user's favorite color if fulfilled,
 * or an error if rejected.
 */
User.prototype.getFavoriteColor = function() {
    // ...
};

(You could also write the type expression as {Promise.<(string|Error)>}, with parentheses around the type union.)

I'm not 100% sold on this syntax, but it seems to me that it offers some advantages over a new tag (or two):

  1. It emphasizes that a Promise is a value returned by a function, not a flow-control keyword such as return or throw.
  2. It's easy to understand after you've seen it once or twice.
  3. JSDoc already supports it! (Really! Try it!)
  4. It doesn't appear to break Closure Compiler projects.
  5. It's concise.

This syntax also has some disadvantages:

  1. It may not make sense the first time you see it.
  2. You have to use one description for both the "fulfilled" and "rejected" types. (I'm not sure this is a bad thing, but some people might not like it.)
  3. At a programmatic level, it's trickier to extract the "fulfilled" and "rejected" types. (I'm not sure whether this matters, but it might matter to someone.)

@hegemonic Your syntax does not scale (it does not support Promises that return multiple error conditions). From a readability point of view, I prefer comment 2 for a solution that's not meant to scale. Besides which, a Promise's return value on success and failure is not really a union. They do not come out of the same callback.

I suggest focusing on a solution that scales first, and providing convenience tags for the more basic case later.

For the scalable syntax, I'm thinking of something like this:

/**
 * Retrieves the employee's state.
 *
 * @return {MyPromise} the asynchronous result
 */
function loadEmployee()
{}

/**
 * @typedef {Promise} MyPromise the result of loading an employee's state asynchronously
 * @return {string} the employee's name
 * @throws {TypeError} if the user is not an employee
 * @throws {ResourceNotFound} if the user was not found
 * @throws {ServerNotAvailable} if the server is busy
 */

I'm not sure whether it makes sense to use @return and @throws for a Promise, but I personally like it. It's as if we're saying that a Promise is like a callback that has already been invoked.

@cowwoc that's really horrible IMHO, it's not DRY it's confusing and way too easy to omit or miss the @typedef. What happens if the function throws synchronously as well?

@phpnode It is DRY: returning a Promise and the Promise itself returning a value is not the same thing. It is also possible for multiple functions to return the same Promise.

If the function throws synchronously as well you end up with this:

/**
 * Sets the employee's name.
 *
 * @param {string} name the employee's new name
 * @return {MyPromise} the asynchronous result
 * @throws {TypeError} if arg is not a string
 */
function setName(arg)
{}

There you go. Now the function throws synchronous exceptions on client-side errors and the Promise returns exceptions on server-side errors.

+1 for using @return and @throws for documenting promise resolution / fails. The concept of throwing and returning are the same in promises only asynchronous.

It should always be possible to document the different async throws and so it makes a lot of sense to use the same semantics.

I do feel that it would make sense to have some sort of inline style as well. This might be usefully for asynchronous functions that never trow or only resolve for timing purposes.

Example:

function delay(seconds){
    return new Promise(function (resolve) {
        setTimeout(resolve, seconds * 1000);
    });
}

I'm sure there are more use cases for this and it might be impracticable having to write a @typedef every time.

I don't think @returns and @throws are the best solutions here, just as I wouldn't document function callback(err, value) {} with @returns {string} value and @throws {Error} err. The concepts are similar, but the code you write to handle a promise is quite different from the code to handle a synchronous method call.

The scaling concern that @cowwoc raised could be addressed through a hybrid of @kylehg's suggestion and my modified version:

/**
 * Retrieve the user's favorite color.
 *
 * @returns {Promise.<string, (TypeError|MissingColorError)>} The user's favorite color
 * if fulfilled, or an error if rejected.
 */
User.prototype.getFavoriteColor = function() {
    // ...
};

That said, I have another concern about this syntax: Because it abuses the syntax that other languages use for generics, it could be confusing or off-putting to developers who've used generics in other languages.

All of the "use @typedef" comments are hinting at another solution, which is to make @promise a synonym for @typedef {Promise}. This is very similar to our implementation of the @callback tag (see issue #260):

/**
 * A promise for the user's favorite color.
 *
 * @promise FavoriteColorPromise
 * @fulfill {string} The user's favorite color.
 * @reject {TypeError} The user's favorite color is an invalid type.
 * @reject {MissingColorError} The user has not specified a favorite color.
 */

/**
 * Retrieve the user's favorite color.
 *
 * @returns {FavoriteColorPromise} A promise for the user's favorite color.
 */
User.prototype.getFavoriteColor = function() {
    // ...
};

This syntax is extremely clear, and as I said, it's pretty consistent with how @callback works. It also allows you to document every single type for a resolved promise. But it won't please people who want the syntax to be as concise as possible.

So I'm trying to document a promise I just made and found this thread. It seems like more concrete examples would be good. For the example below at least it seems like the documentation for the Promise needs to be separate from the function that returns a Promise.

var Promise = require('Promise');
var child_process = require('child_process');

/**
 * @callback ExecResultCallback
 * @param {string} stdout the contents of stdout from the process
 * @param {string} stderr the contents of stderr from the process
 */

/**
 * @promise ExecPromise
 * @fulfill {ExecResultCallback} 
 * @reject {???}
 */

/**
 * @typedef {Object} ExecOptions
 * @property {string?} cwd Current working directory of the child process
 * @property {Object?} env Environment key-value pairs
 * @property {string?) encoding (Default: 'utf8')
 * @property {number?} timeout (Default: 0)
 * @property {number?} maxBuffer (Default: 200*1024)
 * @property {string?} killSignal (Default: 'SIGTERM')
 */

/**
 * child_process.execFile that uses promises.
 * @function
 * @param {string} cmd command to execute
 * @param {string[]} args arguments to command
 * @param {ExecOptions?} options
 * @returns {ExecPromise} 
 */
var execFile = Promise.denodeify(child_process.execFile);

/**
 * child_process.exec that uses promises.
 * @function
 * @param {string} cmdline command and args to execute
 * @param {ExecOptions?} options
 * @returns {ExecPromise}
 */
var exec = Promise.denodeify(child_process.exec);

Unless there are any last-minute objections, I'm going to implement the syntax that I proposed in my last comment, including the new @promise, @fulfill, and @reject tags:

/**
 * A promise for the user's favorite color.
 *
 * @promise FavoriteColorPromise
 * @fulfill {string} The user's favorite color.
 * @reject {TypeError} The user's favorite color is an invalid type.
 * @reject {MissingColorError} The user has not specified a favorite color.
 */

/**
 * Retrieve the user's favorite color.
 *
 * @returns {FavoriteColorPromise} A promise for the user's favorite color.
 */
User.prototype.getFavoriteColor = function() {
    // ...
};

@greggman: We'll have more examples once the syntax is finalized.

@hegemonic still think this is _way_ too verbose and no one will use it as a consequence. Your proposal is _completely_ impractical for code which makes heavy use of promises. It's also misleading, the example is not returning a FavoriteColorPromise instance. It's returning a Promise instance.

@phpnode, I totally understand your concerns about verbosity. You can always write the following instead, as long as you don't care about documenting the error case:

/**
 * Retrieve the user's favorite color.
 *
 * @returns {Promise.<string>} A promise that contains the user's favorite color
 * when fulfilled.
 */
User.prototype.getFavoriteColor = function() {
    // ...
};

I also hear what you're saying about the fact that FavoriteColorPromise is not a real type. But the @typedef and @callback tags follow that same model, so I'm afraid the ship has sailed already.

@phpnode, I actually disagree with you. The function _does_ return the aforementioned the promise. The fact that the promise eventually returns another value doesn't change that fact. You have no predicting (promising to the user) what asynchronous value the function will return because someone could chain a followup promise which returns a different value than you expected. The only thing we can and should be documenting is the synchronous result, as @hegemonic is suggesting.

You have no predicting (promising to the user) what asynchronous value the function will return because someone could chain a followup promise which returns a different value than you expected

@cowwoc do you write a lot of idiomatic promise driven code? I don't think you'd have said this if you do. A chained promise _creates a new promise object_, it does not mutate the original promise. So the promise returned by a function will always yield the same value.

The fact that a promise is being returned is not _particularly_ interesting when almost all your code returns promises. The interesting point is what that promise resolves to. That's why I still think that this syntax is the most concise option:

/**
 * @promise {Boolean} if something is true then true.
 */

which will be sufficient for probably 95% of the cases out there. If you really need to document the failure mode (vast majority of cases don't even have a failure mode to document), then use @fail

/**
 * @promise {Boolean} if something is true then true.
 * @fail {TypeError} if something went wrong
 * @fail {OtherError} if something else went wrong
 */

It's much easier to reason about code when you can say "this function promises a Boolean", rather than "this function returns a Promise which encapsulates a Boolean". The return value is not interesting.

A chained promise creates a new promise object, it does not mutate the original promise. So the promise returned by a function will always yield the same value

@phpnode Sorry, good point.

My only concern with your syntax is whether it scales. What happens if Promise A returns Promise B which returns Promise C? I assume you're saying that we should document the final return value of C and all error conditions returned by A, B and C. Is that correct?

That could work so long as the Promise really does nothing else but fulfill and reject (meaning, you didn't add user-defined properties to it for some reason). Does anyone see a problem with this approach?

@cowwoc we'd only document the final return value and yes any errors that could be caused directly by the function itself.

/**
 * Load a user.
 * @param   {Number} id   The id of the user to load
 * @promise {User}        The loaded user instance.
 * @fail    {InputError}  If the id is invalid.
 */
function getUser (id) {
  if (typeof id !== 'number') {
    return Promise.reject(new InputError('A user id must be specified'));
  }
  return this.database
  .get(id) // this could reject with any number of failures, we don't document them here
  .then(function (result) {
    return new User(result);
  });
}


New here but, writing a bunch of Promise-based code, I so _very_ much agree with @phpnode on this one. I'd love to see @promise and @fail.

(I personally would be fine with @rejects, also: I've caught myself saying things like "this function rejects with such-and-such on failure" as a convenient shorthand. But @fail is fine with me.)

I don't like @fail. I'm trying to avoid the fail keyword as it seems Kris Kowal's Q proprietary. Fail, as a method, is not supported by angular's $q nor petkaantonov's Bluebird (I think?).

I would opt for @resolve, @reject and @notify to be consistent with promise terminology (the deferred API).

What about function parameters that can be promises as well? For now only the typedef and generics approaches cover that. I agree that for simple cases simple notations are valuable, but let us not provide this feature partially (for returning promises only).

+1 for @promise analogous to @callback

I'm sure I am late to the party, but having used promises all over my code I too would love for a simpler and more concise way of writing promise docs.

I think @phpnode is the best I have seen so far after reading through this thread.

Yes I think that the @typedef/@callback style is more flexible, but I am not going to write all of that for every unique promise I have. That is way too much for simple promise returns. For complex promise returns or promises as params, sure I will write verbose, but for that vast majority of my simple promise returns, @phpnode way works best. Just my opinion since this is still open.

Also late to the party, but why not keep to @param and @returns to stay consistent with current mechanics (they _are_ args and return values, after all). And if need be, use optional @resolves and (one or more) @rejects to clarify certain behavior of a promise if need be, in a @property kind of way.

/**
 * @param {Promise<string, Error>} p
 * @resolves {string} p When it does something right
 * @rejects {TypeError} p When it does something wrong
 * @rejects {SuperError} p When it does something super wrong
 */
function f(p) {}

This also keeps it clear in case a function handles multiple promises (as parameter and return value), as this discussion mainly seems to concern itself with _returning_ a promise, instead of accepting one.

It also seems like a more consistent experience in general.

@qfox

  1. DRY: you're making me repeat which promise is returning an error or value.
  2. How would you specify resolve/reject for return types? (there is no variable name to reference)

Future versions of ECMAScript are going to introduce an async keyword, like this:

async function myFunc () {
  return true;
}

When invoked, async functions always return promises and capture any exceptions thrown and turn them into promise rejections. Async functions _promise_ to return a value.

With this in mind, how do you document the return value of this function?

In my mind @promise is the only way forward, using @returns here would be incredibly misleading.

@cowwoc

The clarification is optional and should probably often be omitted. If that's not the DRY you're talking about, please clarify which part you find repeating.

Return types is a different matter. But the same could be said about unclassified @resolves and @rejects (or @promise, @fail, etc). I tend to name my returns if it clarifies things, maybe a similar approach can be taken. Not sure if that breaks any existing syntactic rules of @returns, though.

@phpnode

You said it yourself, it _returns_ promises. So you call that async function and you'll get a promise value back. Ok let me put it differently, how do you annotate a factory function? So a function returning another function? I see this as a very similar thing.

@qfox when virtually every function in your codebase returns a promise, the fact that a promise is being returned is not interesting. What is interesting is the value being promised.

I really feel that a lot of people in this discussion are not actually using promises much. For example it's incredibly rare to pass promises as parameters to functions, yet almost all of the examples here focus on it.

What _is_ important is that there is a conceptual difference between returning a value and promising a value. I said this upthread, but it's much easier to think of a function which promises a value than a function which returns a promise which encapsulates a future value.

FWIW I'm continuing to use @promise {SomeType} and it works well, it would be nice if jsdoc would support it.

Just to clarify, these are similar ways in which I would use them, depending on whether certain things need clarification. Using a train catching analogue in an attempt to reduce the obviousness of DRY due to an artificial example.

/**
 * @param {Promise<Train, Error>} p Waits for you to catch the train
 */

/**
 * @param {Promise<Train, Error> p Waits for the train
 * @rejects {LateError} p When you miss the train
 * @rejects {DoorError} p When you get there and the train is there but doors are closed and train leaves as you touch the handle
 * @rejects {LostError} p When you can't find the train station
 */

/**
 * @param {Promise<Train, Error> p Waits for the train
 * @resolves {Train} p The value will contain itinerary, make, model, and applause for catching the train on time
 */

@phpnode your comment seems to project your own personal experience and style onto how others are using it. It feels short sighted to simply ignore promises as arguments. I've encountered them frequently like that. And I'm still curious how you annotate functions returning functions. Do you use @factory or just @returns {Function}?

@qfox I don't think the doc examples you posted really make sense without any code to back them up, would you mind putting together a couple of functions to show when this is useful?

And I use @returns {Function}. For this kind of stuff I'm looking to provide practical information which helps developers reason about the codebase, so if it's ambiguous, I'll add a comment explaining what's going on. It's impossible and pointless to express all of the different scenarios via special @tags, so I don't even try. But promises are super common and now a core language feature, so they warrant their own shortcut.

Sigh :)

/**
 * @returns {Promise<Store, Error>} dataStore
 * @rejects {NetworkError} dataStore
 * @rejects {AccessError} dataStore
 * @rejects {TimeoutError} dataStore
 */
function getDataStore() { /* some async stuff to setup a data store from network */ }

/**
 * @param {Promise<Store, Error>} dataStore See getDataStore
 */
function report(dataStore, key) {
  dataStore
    .then(function(d) { /* report the key somehow */ }
    .otherwise(function(e) { /* report the error, there may be bigger fish to fry */ }
}

/**
 * @var {Promise<Store, Error>} 
 */
var store = getDataStore();

// ... lots of sync/async code here, then at some random point like a console.log

report(store, 'importantKey');

You'd pass on the data store without having to know that it loaded and the report would happen as soon as the promise resolves. It's a bit of a contrived example, of course, but that's what you asked for :)

It'd be prettier if we could use sub-bulleted list for the resolve/rejects :) That would also prevent repeating the var name over and over. And solve the anonymous return value case. But it doesn't feel very jsdoc-ish.

@returns {Promise}
- rejects {NetworkError}
- rejects {TimeoutError}

@qfox ok, I can see how this can be potentially useful in some very limited circumstances, but the example is indeed pretty contrived,

function reportSuccess () {}
function reportError () {}
getDataStore().then(reportSuccess, reportError);

anyway, there is no reason why @promise {Foo} couldn't be syntactic sugar for @returns {Promise<Foo>}, then everyone's happy.

I'm mainly looking for a better way to annotate promises than just the ugly @returns {Promise} Resolves with something when A. Rejects when B. But I guess there's no consensus about that. Yet.

I think the real "issue" is that JSDoc only quasi-supports documenting "inline, one-off types", of any sort. An example of where it is quasi-supported is with @param {} x, @param {v} x.a, or @param {{name: string, age: integer}} - player information etc.

An example of an "inline, one-off type" may look like so and is effectively just a way of applying a "typedef" without creating an explicit type.

/**
* @param {Promise<string, Error>} p - generics are cool too
* @return {{
*   (hint: it's implicitly a @promise)
*   @resolves {string} p When it does something right
*   @rejects {TypeError} p When it does something wrong
*   @rejects {SuperError} p When it does something super wrong
* }} a promise for making toast
*/

And with re-envisioning of the player; note that additional such an expanding syntax also allows the collection of other (useful) type information that was previously lost. Also, I'm making a point - so don't bring up the previously mentioned way of doing this with multiple @param..

/**
* @param {{
*   @property {string} - full name in ML format
*   @property {integer} - age of the player, if they are an adult
* }} - player information
*/

(This could naturally be applied for N levels.)

Unfortunately, to introduce such changes is likely a non-trivial matter - and worse would be the lag from tools that "sorta" understand jsdoc.

@pnstickne agreed. I tend to solve clarifying important properties of params with @property like so;

/**
 * @param {Object} options
 * @property {string} options.foo Stuff goes here
 * @property {string} options.bar And here
 */

That's why I figured a similar approach would work well for promises as well.

I'm against the "inline, one-off types" syntax. In my opinion, this design does not scale and results in reduced readability.

@cowwoc Even in a bullet/indent or parent.child forms?

@pnstickne Sorry, I don't understand what you mean. Can you provide an example of what that documentation would look like?

@cowwoc There have been at least 3 different thoughts of representing roughly the same thing.

Mine, with the very non-JSDoc like nesting. Understandably this runs counter to JSDoc syntax and does start to feel too complex. But what about sub-items or name-chaining (as already done in @param)?

@returns {Promise}
- rejects {NetworkError}
- rejects {TimeoutError}
 * @returns {Promise<Store, Error>} dataStore
 * @rejects {NetworkError} dataStore
 * @rejects {AccessError} dataStore
 * @rejects {TimeoutError} dataStore

@pnstickne I'm not a fan of having to repeat dataStore multiple times. Repetition is the universe's way of telling you you are doing something wrong. If you isolate dataStore into it's own type (instead of treating it as a variable coming off the first type) then you won't have to keep on specifying what type/variable you are referring to.

I'm in favor of:

/**
 * Looks up a company by it's name.
 *
 * @param {String} name the company name
 * @return {MyPromise}
 */
function getByName(name)
{}

/**
 * @promise MyPromise
 * @resolve {URI} the company URI
 * @reject {TypeError} if name is empty
 * @reject {NetworkError} if a network error occurs while making the request
 */

A Promise is not a "simple" data type (it returns a minimum of two values). As such, I believe it requires a separate documentation block even for one-off types.

Even if you disagree, we know for a fact that some use-cases will reuse the same type of Promise multiple times, and for those use-cases there is little doubt we will need a separate documentation block. Start off by adding this to JSDoc and keep on working on a separate definition for one-off promises if you so wish.

This looks good to me:

/**
 * Looks up a company by it's name.
 *
 * @param {String} name the company name
 * @return {MyPromise}
 */
function getByName(name)
{}

/**
 * @promise MyPromise
 * @resolve {URI} the company URI
 * @reject {TypeError} if name is empty
 * @reject {NetworkError} if a network error occurs while making the request
 */

Looking forward to a decision :)

I'm still inclined to think that the syntax from my earlier comment is the most appropriate solution. This is almost identical to @cowwoc's most recent proposal, except that it uses @fulfill instead of @resolve--a promise is "resolved" when it's either fulfilled or rejected. (Or at least that's what the Promises spec said in July; maybe it's changed since then.) That said: In the real world, I think people tend to say "resolve" when they mean "fulfill," so I'm open to defining @resolve as a synonym for @fulfill.

As a reminder for everyone who finds that syntax too verbose, you can always do this instead, as long as you don't care about documenting the error condition:

/**
 * Retrieve the user's favorite color.
 *
 * @returns {Promise.<string>} A promise that contains the user's favorite color
 * when fulfilled.
 */
User.prototype.getFavoriteColor = function() {
    // ...
};

I don't want to adopt a solution that adds new syntactical constructs to JSDoc, or changes existing JSDoc tags, or uses new terms that aren't consistent with the Promises spec. I also don't want a solution that makes it impossible to document the error condition, should you want to do that.

I recognize that some people are extremely unhappy about the proposed syntax, but I don't think there's any way to satisfy everyone who's contributed to this discussion. If you aren't willing to use the new syntax, you can always write a plugin to implement any behavior you want.

@hegemonic To the language, returning a Promise is just like returning anything else, but to us, that means the function doesn't actually return anything. When you call a function like getUser(), you are trying to get an object that describes a user. You are not thinking of calling getUserPromise(). Your aim is never reaching a promise; it's always reaching a value. If we define a whole different way of documenating for properties and methods, we should have a whole new way of documenating promising methods, callback-taking methods and hybrids too.

Take this example:

/*
    Using a property
    It is false when it's bad
*/
//Getting the result
var result = myObj.result;

//Using the result
if (!result)
    return false;

/*
    Using a property
    It has .error when it's bad
*/
//Getting the result
//What the property value is has changed, but the usage of the documented feature is the same
var result = myObj.result;

//Using the result
if (result.error)
    return false;




/*
    Using a method
    It is false when it's bad
*/
//Getting the result
var result = myObj.result();

//Using the result
if (!result)
    return false;

/*
    Using a method
    It has .error when it's bad
*/
//Getting the result
//What the method returns has changed, but the usage of the documented feature is the same
var result = myObj.result();

//Using the result
if (result.error)
    return false;




/*
    Using a promising method
    It is false when it's bad
*/
//Getting the result
myObj.result().then(check);

//Using the result
var check = function (result) {
    if (!result) {
        return false;
    }
}

/*
    Using a promising method
    It has .error when it's bad
*/
//Getting the result
//What the promise resolves to has changed, but the usage of the documented feature is the same
myObj.result().then(check);

//Using the result
var check = function (result) {
    if (result.error) {
        return false;
    }
}

In this example, you can see that:

  • A property's type, a method's return and a promising method's resolved value has a very close relation.
  • What actually changes between a property and a method is how they give out their values and the change between a synchronous method and a promising (or callback-taking or hybrid) method is the same.

So if we are not documentating methods like properties having a function value:

/**
 * A function for the user's favorite color.
 *
 * @function FavoriteColorFunction
 * @returns {string} The user's favorite color.
 * @throws {TypeError} The user's favorite color is an invalid type.
 * @throws {MissingColorError} The user has not specified a favorite color.
 */

/**
 * User's favorite color.
 *
 * @type FavoriteColorFunction
 */
User.prototype.getFavoriteColor = function() {
    // ...
};

we shouldn't documentate promising, callback-taking and hybrid methods like in your example.

I don't know why you want to mix @returns, @throws, @promises, etc. Sugar should be a sugar. Please don't squash them all to one thing.

/**
 * @returns {?Promise.<string|string[]|{a:string,b:string[]},MyError|ConnectionError,EtcError>|string}
 *   the same you can use for @typedef or @param
 * @throws ThrowingError we need it for sync. we can't describe here async throws for promise
 */
function asd () {}

/**
 * Probably it's also can be supported. But it's much harder to read:
 * @typedef {Promise.<String|Number>} MyPromise
 * @property {String} noname ?
 * @property {String} anothernoname ?
 * @throws ThrowingError for async?
 */

It's great to see jsdoc getting promise support! Quite an interesting (and epic!) discussion :)

I think it's important that "fulfill" and "resolve" not be portrayed as synonyms. While some people do use them that way, that's not the intention. Promises/A+ intentionally specifies the term "fulfilled" to mean the success state, and ES2015 follows that. It also intentionally uses the terms "resolve" and "resolution" to mean an _algorithm_.

IOW, to promise consumers, "fulfill" is observable, "resolve" is not. I feel it would perpetuate confusion about the two terms if jsdoc allowed them as synonyms.

Promises have a strong analogy to synchronous return and throw. This is also intentional, so that reasoning about async, promise-based functions is very much like (but not identical to) reasoning about sync functions. Given that, it seems to make sense that documenting promise-returning functions should be very much like (but also not identical to) documenting sync code. That makes me think there should be async analogs to @returns and @throws.

It'll be a while before ES7 async/await syntax is viable, but it may be worth thinking about how jsdoc might document those, and seeing if it makes sense to use that same approach for promises now (or maybe that'd just turn out to be confusing, I dunno).

As for mixing @throws and async-tag-that-is-analogous-to-@throws, promise-returning functions should never throw synchronously. Like everything, there are exceptions to that rule in the real world, such as up-front argument type-checking--but that's more like a compiler error than a runtime error. It seems like documenting parameters types and chalking type-checking exceptions up to "compiler errors" may be good enough.

I don't see a strong need for jsdoc sugar for promises-as-arguments. The primary intended use of promises is as return values, which is why they model sync return and throw as closely as they do. In the longer term with ES7 async/await, having promises be passed as arguments will likely be even more rare than it is today.

@phpnode

that's really horrible IMHO, it's not DRY it's confusing and way too easy to omit or miss the @typedef. What happens if the function throws synchronously as well?

If your function returns Promise and in the same time may throw exception synchronously — you're doing it horribly wrong.

Just think why Promise in ES6 implemented like this:

new Promise(executor);
new Promise(function(resolve, reject) { ... });

(Excuse me, if this was already pointed out. There's a lot of comments...)

This thread is very interesting, but I feel it lacks a good example that is slightly longer. The below is a made up version of some model class written using ES6 (and a ES7 decorator). I tried a few of the suggested styles, but not the @typedef/@callback version, which I do like for the more complicated cases.

import Model, {schema} from "some-ORM-library"
import {Parser, HtmlRenderer} from "commonmark"

import User from "./User"
import {AuthorizationError, ValidationError} from "./errors"

@schema({
  title: String,
  text: String,
  publishedAt: Date,
  author: User
})
export default class Post extends Model {
  /**
   * @param {string} source in Markdown format
   * @return {string} the source rendered as HTML
   */
  static render(source) { return this.html.render(this.markdown.parse(source) }
  static markdown = new Parser()
  static html = new HtmlRenderer()

  /**
   * @param {string} input to set as title
   * @return {Promise.<string>} for the title of this post
   * @rejects {ValidationError} if the input is too long
   */
  set title(input) {
    if (input.length > 72) Promise.reject(new ValidationError("Title too long"))
    return this.set("title", input)
  }

  /**
   * @promise {string} the HTML for the text of this post
   */
  get text() { return this.get("text").then(Post.render) }

  /**
   * @param {(Object|User)} user to set as author
   * @return {Promise}
   * @fufills {User} that is the author of this post
   * @rejects {AuthorizationError} if the user isn't allowed to be the author
   */
  set author(input) {
    return Promise.all([this.get("author"), User.from(input)])
      .then(([self, other) => {
        if (self.is(other)) return self
        if (self == null || other.isAdmin) return this.set("author", other)
        throw new AuthorizationError("Operation not allowed")
      })
  }
}

I would like to focus you that we have to keep docs API as simple, consistent and flexible as it possible.
In this way the best syntax should looks similar to@returns keyword.

/**
 * My check permission example
 * @param {String|Object}  params Permission name or custom params
 * @param {String} [params.user] User name 
 * @param {String} [params.host] Host name 
 * @returns {Promise.<Boolean|{{user: String, allowed: String[], denied: String[]}}, ConnectionError|ValidationError>}
 */ 

Also should be possible:

/**
 * @typedef {Object} Permission
 * @property {String} user 
 * @property {String[]} allowed
 * @property {String[]} denied
 */
/**
 * My check permission example
 * @param {String|Object|Object[]}  params Permission name or custom params
 * @param {String} [params.user] User name 
 * @param {String} [params.host] Host name 
 * @returns {Promise} result
 * @resolves {Boolean} result when specified only permission name
 * @resolves {Permission} result when specified custom params 
 * @resolves {Array.<Permission>} result when specified multiple custom params
 * @rejects {ConnectionError|ValidationError} result
 */ 

Promise as a param - example 1:

/**
 * Secured action
 * @param {Promise.<Permission, ConnectionError|ValidationError>] [check]
 * @returns {Promise} result
 */

Promise as a param - example 2:

/**
 * Secured action
 * @param {Promise.<{{user: String, allowed: String[], denied: String[]}}> [check]
 * @returns {Promise} result
 */

Promise as a param - example 3:

/**
 * Secured action
 * @param {Promise} [check]
 * @resolves {String} [check.user] user name
 * @resolves {String[]} [check.allowed] allowed actions
 * @resolves {String[]} [check.denied] denied actions
 * @rejects {ConnectionError|ValidationError} [check]
 * @returns {Promise}
 */

I like that last comment. Both readable, scalable and short enough that we'll use it.

As a suggestion I'd use a paste tense to indicate the various states: @fulfilled or @resolved (could be synonyms?) instead of @resolves and @rejected instead of @rejects. Basically means "when resolved, ...". Also this shows well that the consumer is interested in the current status only.

Also, we need a way to indicate a Promise without a value (or more strictly an undefined value ?) Promise.<void> ? Promise.<undefined> ?

I'm personally against {Promise.<{{user: String, allowed: String[], denied: String[]}}> in all its forms. I find it hard to read/understand and it obviously doesn't scale.

The other syntax that combines @param with @resolves and @returns is not bad.

I'm personally against {Promise.<{{user: String, allowed: String[], denied: String[]}}> in all its forms. I find it hard to read/understand and it obviously doesn't scale.

I don't get your objection. If what you're against is the fact that the value structure is too long, the object could be made a @typedef elsewhere to make it more readable.
What you object is really a style difference that should not impact what will be decided here, as long as there are other options that suit you. For simple values (like Number or String) it's really nice to be able to use Promise.<String>.

To be fair, this is a more of a complaint I have against jsdoc in general. I'm in favor of simple type parameters (e.g. Promise.<String>) but against anonymous object parameters (e.g. Promise.<{{user: String, allowed: String[], denied: String[]}}). As far as I'm concerned, all objects should be declared using a separate typedef. That said, I think this discussion is out of scope for this issue.

Being a sqeaky wheel here, but please do consider @fulfills instead of @resolves. "Resolves" isn't the opposite of "rejects", and "fulfills" is the correct terminology as per Promises/A+ and ES2015.

For example, a promise can be resolved to another _pending_ promise (see the last paragraph in the ES2015 spec section linked above). That is not observable and wouldn't make sense to document. The ultimate fulfillment value is the only thing that will be observable.

Whichever way this ends up going, please also consider the async/await syntax which is almost certainly going into ES2016 and many people are already using via Babel. Async functions always return promises, _but when you're using async/await you don't care_ you just care about the future value, so, how do you document this function? What is the _return value_?

async function getUser (id) {
  const record = await X.select("users", id);
  return new User(record);
}

@phpnode :+1: for the reminder to think about async/await

but when you're using async/await you don't care

As a caller of an async function, you may care a little, because you need to know to await the result. I totally agree tho that the "return type", iow the "fulfillment type", of the result is most important.

It's an interesting thought to ask whether it's even necessary to document @fulfills and @rejects in an ES7 world. IOW, would simply using @returns and @throws would be the right thing, and let async be the cue (to humans and parsers) that the actual result is wrapped in a promise ...

@briancavalier in my opinion the answer is yes, the issue becomes that the function above is semantically identical to:

function getUser (id) {
  return X.select("users",id).then(record => new User(record));
}

and I think that having two syntaxes to describe this same thing is obviously problematic. In light of async await, I'd probably consider something like this:

/**
 * @returns {User}
 */
async function getUser (id) {
  const record = await X.select("users", id);
  return new User(record);
}

and therefore,

/**
 * @async
 * @returns {User}
 */
function getUser (id) {
  return X.select("users",id).then(record => new User(record));
}

@phpnode The simplicity of your last example is _extremely_ attractive, imho!

I also like @phpnode's syntax, though I would point out that it implies that the return value is either 100% async or not. It is technically possible (though confusing) to have a function which returns some values/exceptions asynchronously and others synchronously.

For example, webrtc used to throw some exceptions synchronously (e.g. invalid input) and others asynchronously (e.g. an I/O error occurred). Perhaps I think such design is extremely confusing and should be discouraged but are we will to make them undocumentable? :)

@cowwoc ņ̶o͟҉t̶̴̴ ͟s͟u҉p̸̨p͜o̧̕r̢̢t̸̡i̶n͘g͢ z҉al̵̀g̡͟͡o ̡͠m͘o̶̷̢d͠͠e̵̛ ̵̛͡i̸s̶͞ a ̡́͠f̴e҉͘at̶ur̴̛͞e

@phpnode What? :)

a function which returns some values/exceptions asynchronously and others synchronously

Sadly, yes.

some exceptions synchronously (e.g. invalid input)

I actually think that's ok. It's really a compile error, and it _should_ utterly break the app.

Perhaps I think such design is extremely confusing and should be discouraged but are we will to make them undocumentable

I agree it's extremely confusing and should be discouraged to allow "runtime" (vs "compile time") errors to be either sync or async. And I actually think @phpnode's suggestion _does_ discourage it!

IMHO, if someone does allow sync and async and documents it with @async (or _doesn't_), then the documentation would be no more confusing than the function itself! So no additional harm done :D

@cowwoc "not supporting Zalgo mode is a feature". More seriously, in such a scenario, the function is _not_ unambiguously async, and shouldn't be labeled as such. So if you really want to do that, @returns {Promise} is probably acceptable, but JSDoc should not make such insanity convenient or easy.

@phpnode Makes sense. You've got my vote :)

PS: What is "Zalgo mode"? Nevermind, got it: http://www.eeemo.net/

@phpnode, dude, wash your keyboard :)

@cowwoc when you have an API which sometimes delivers results synchronously and sometimes asynchronously, you unleash Zalgo and bad, terrible things happen, here's an essay on it - http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony

Locking discussion on this issue because it's gotten way out of hand.

Unlocked by request. If you add a comment, please keep it on-topic.

In VS Code and TypeScript, it’s possible to use @return {Promise<T>}, where T is the type of the resolved value.

@ExE-Boss That’s also the recommended syntax in JSDoc.

I see it is used in an example, but might it be documented on the types page?

Tuan Pham

What about to automatically turn @async functions to Promises?

/**
 * @async
 * @return {string}
 */
function asyncFoo() { return Promise.resolve('test') }

And the generated doc would become @return Promise<string> ? Similarly for ES2017 async keyword.

It seems one convoluted way to work around the lack of support in plain jsdoc for specifying rejectors is to use @interface itself have @class on it as well, such that, for example, one could avoid subclassing Promise yet indicate the constructor follows the signature of the constructor, e.g.:

/**
 * @callback SpecialResolver
 * @param {string|number|whatever} resolutionValue
 */

/**
 * @callback SpecialRejector
 * @param {TypeError|SyntaxError} rejectionValue
 */

/**
 * @callback SpecialExecutor
 * @param {SpecialResolver} resolve
 * @param {SpecialRejector} reject
 */

/**
 * @interface
 * @class SpecialPromise
 * @implements Promise
 * @param {SpecialExecutor} executor
 */

A standard tag for @rejects or an ability to pass in as a template (generic?) like Promise<MyResolver,MyRejector> would be far more succinct of course, but the above seems to work now in plain jsdoc.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kilianc picture kilianc  ·  89Comments

kripod picture kripod  ·  26Comments

gajus picture gajus  ·  15Comments

FezVrasta picture FezVrasta  ·  23Comments

gocreating picture gocreating  ·  18Comments