-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature: Documenting Promises #509
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:
What do you think? |
I've been using a syntax that's basically identical to /**
* @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 - |
I'm not against the idea of 2 tags per-se but I dislike the names you chose. "A function |
@cowoc I actually agree that |
I'm not sure why we'd need to change anything for /**
* @param {Promise} p a promise.
*/ |
But it would be much clearer if one would have an ability to specify some details about this /**
* @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 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 |
+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 /**
* @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 The way I see it, Promises |
@kennknowles sure, being able to do it the more verbose way as well would be great, using |
@cowwoc multiple /**
* 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: |
I'm 100% for using |
Thanks to all of you for contributing your ideas! I have to say that I agree with @cowwoc's comment earlier in this discussion:
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 I'm not 100% sold on this syntax, but it seems to me that it offers some advantages over a new tag (or two):
This syntax also has some disadvantages:
|
@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:
I'm not sure whether it makes sense to use |
@cowwoc that's really horrible IMHO, it's not DRY it's confusing and way too easy to omit or miss the |
@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:
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 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 |
I don't think 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 /**
* 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 |
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.
|
Unlocked by request. If you add a comment, please keep it on-topic. |
In VS Code and TypeScript, it’s possible to use |
@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? |
The issue most probably exists in jsdoctypeparser module. Based on its tests this is expected behaviour, possibly outdated way of typing Promises. jsdoctypeparser test fixture: https://github.com/jsdoctypeparser/jsdoctypeparser/blob/d4ea94783dc34f9ea527551d2db66c94761eae0b/tests/fixtures/catharsis-types#L60 Reference: jsdoc/jsdoc#509 (comment)
What about to automatically turn /**
* @async
* @return {string}
*/
function asyncFoo() { return Promise.resolve('test') } And the generated doc would become |
It seems one convoluted way to work around the lack of support in plain jsdoc for specifying rejectors is to use /**
* @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 |
I agree with @brettz9's recommendation of |
FWIW, it seems TypeScript has such a proposal offered there too: microsoft/TypeScript#39680 |
when a function is async then i wish i didn't have to type /**
* @return {string} some desc
*/
async function() {
return globalThis.xyz
} and the IDE would automatically translate it to |
@jimmywarting I dont think that's really an option since promises dont have to be awaited. And a promise which is not awaited returns a promise. E.g. const asyncFn = async () => {
return await Promise.resolve('Hello');
};
const notAwaited = asyncFn(); // returns Promsie<string>
const awaited = await asyncFn(); // returns string |
Jimmy's point is that an |
That was a bit what i was getting at... it sometimes feels too verbose sometimes to write in my terminology: A async function is just a function that when called happens to wrap everything in a promise with the returned value invoked, no matter what you return. /** @return {string} */
async function foo () {
return "abc" // The return value is actually a `string` not a promise
} /** @return {Promise<string>} */
async function foo () {
// here we do actually return a promise, therefor you should
// specify in the `@return` that you are actually returning a promise promise
return Promise.resolve("abc")
} I kind of look at both normal function and async function as two different kinds of functions that does different things (() => {}).constructor // ƒ Function() { [native code] }
(async () => {}).constructor // ƒ AsyncFunction() { [native code] }
(async () => {}).constructor === (() => {}).constructor // false |
The thing that you pass to ETA: I think we might be talking past the point here. The audience for JSDoc is the caller, first and foremost. It can be helpful for the implementer, I guess, but you're documenting it for whoever is going to call the function, and that's why the actual output (always a Promise) is most important. |
yea, true... in the end you will always end up with a returned if (foo.constructor.name === 'AsyncFunction') {
throw new Error("Your function need to be sync, cuz fs.readSync() don't expect a promise")
} else {
return foo()
} Therefore i also think it might be important to distinguish normal function from async functions as well. kindof like: /**
* @returns {AsyncFunction}
*/
globalThis.xyz = function xyz () {
return async () => {};
};
/**
* @returns {Function}
*/
globalThis.xyz = function xyz () {
return () => {};
}; if i write this kind of code then i would expect to see a error at the end of this snippet in my IDE const foo = async () => {}
const AsyncFunction = foo.constructor
/** @param {AsyncFunction} fn */
function xyz(fn) {
fn().then(console.log)
// ^-- the IDE should know that it returns a promise
}
// should roughly be equivalent to this typescript `async function xyz(fn: () => Promise<unknown>)`
// expect-error
xyz(() => {}) |
I don't believe anyone's observed this yet, but the proposed workaround syntax, More pertinently, perhaps — it does not catch the eye of the casual reader the way a specific, correct tag would, which is, imho, the entire purpose of unchecked type claims. |
The problem of capturing reject-type is, who provides your Promise type definition? I don't have data to back this up, but I strongly suspect a large percentage of folks watching this issue are using JSDoc via TypeScript and/or VSCode integration. If your Promise type is coming from somewhere else, maybe it supports a second generic argument (as in #509 (comment) and following comments), but TypeScript does not. See microsoft/TypeScript#13219 (comment) , which probably means that microsoft/TypeScript#39680 is not happening any time soon. If your Promise type doesn't support strong types for rejection, the only way forward for JSDoc would be adding something like |
Problem
All my projects now have functions that returns promises.
Idea
How about some specific promise documentation?
Example
Following this spec: http://promises-aplus.github.io/promises-spec/
The text was updated successfully, but these errors were encountered: