82

I've spent quite a while scouring the internet looking for the best way to properly document callbacks with jsdoc, but unfortunately, I haven't found a great one yet.

Here's my question:

I'm writing a Node.js library for developers. This library provides multiple classes, functions, and methods that developers will be working with.

In order to make my code clear and understandable, as well as to (hopefully) auto-generate some API documentation in the future, I've started using jsdoc in my code to self-document what's happening.

Let's say I define a function like the following:

function addStuff(x, y, callback) {
  callback(x+y);
});

Using jsdoc, I'm currently documenting this function as follows:

/**
  * Add two numbers together, then pass the results to a callback function.
  *
  * @function addStuff
  * @param {int} x - An integer.
  * @param {int} y - An integer.
  * @param {function} callback - A callback to run whose signature is (sum), where
  *  sum is an integer.
  */
function addStuff(x, y, callback) {
  callback(x+y);
});

I feel like the above solution is kinda hack-ish, since there's no way for me to specify in absolute terms what the callback function should accept.

Ideally, I'd like to do something like:

/**
  * Add two numbers together, then pass the results to a callback function.
  *
  * @function addStuff
  * @param {int} x - An integer.
  * @param {int} y - An integer.
  * @param {callback} callback - A callback to run.
  * @param {int} callback.sum - An integer.
  */
function addStuff(x, y, callback) {
  callback(x+y);
});

The above seems like it'd allow me to more simply convey what my callback needs to accept. Does that make sense?

I guess my question is simple: what's the best way to clearly document my callback functions with jsdoc?

Thank you for your time.

rdegges
  • 27,994
  • 16
  • 73
  • 100
  • 1
    Possible duplicate of [How to document callbacks using JSDoc?](http://stackoverflow.com/questions/13403887/how-to-document-callbacks-using-jsdoc) – Victor Sergienko Feb 09 '16 at 19:51

5 Answers5

107

JSDoc 3 has a @callback tag for exactly this purpose. Here's a usage example:

/**
 * Callback for adding two numbers.
 *
 * @callback addStuffCallback
 * @param {int} sum - An integer.
 */

/**
 * Add two numbers together, then pass the results to a callback function.
 *
 * @param {int} x - An integer.
 * @param {int} y - An integer.
 * @param {addStuffCallback} callback - A callback to run.
 */
function addStuff(x, y, callback) {
  callback(x+y);
}
ᆼᆺᆼ
  • 14,442
  • 8
  • 53
  • 87
Jeff Williams
  • 1,791
  • 1
  • 15
  • 9
  • 21
    This isn't supported by Visual Studio IDE. The other answer here, `@param {function(int):void} callback` works in VS and is also much more concise. –  Sep 12 '17 at 03:36
  • This is what I ended up doing in Visual Code. – luis.espinal Sep 26 '17 at 14:26
  • 1
    If this were a callback to add two numbers, the callback should have two parameters. I believe we want the callback line to say `callback(x,y)` and to add another @param in the Callback docstring. – johnklawlor Feb 25 '18 at 16:16
  • @johnklawlor, I don't think so. This is a simplification on the part of the OP. When his library is finished working with the two numbers obtained elsewhere perhaps earlier but in this case from his `addStuff` method, the result is given back to the client by calling the `callback` function that was supplied by the client. The goal of this question is to make it clear to the client, the user of the library, what parameters that callback function should have. The client would have a function with any name: `function addStuffResult( res ) { /* do stuff with res */ }`. – aamarks Jun 10 '19 at 15:48
46

Another possibility is to describe the value passed to the callback this way:

/**
  * Add two numbers together, then pass the results to a callback          function.
  *
  * @function addStuff
  * @param {int} x - An integer.
  * @param {int} y - An integer.
  * @param {function(int)} callback - A callback to run whose signature is (sum), where
  *  sum is an integer.
  */
function addStuff(x, y, callback) {
    callback(x+y);
});

To document the return type of the callback, use @param {function(int):string}.

Yaki Klein
  • 2,584
  • 2
  • 30
  • 31
  • 2
    I liked this method as my IDE (Netbeans) supports it but does not seem to recognise the @callback tag – Justin Emery Dec 05 '16 at 12:23
  • 1
    what about including the return value of the `function(int)` ? – Maslow May 04 '17 at 20:50
  • 7
    @Maslow `function(int):string` (as an example) works for me in Visual Studio Code. – Inkling May 05 '17 at 10:25
  • 1
    Just found out using `@param {function(error):void} callback - Returns null if no error occurred.` works but it says "arg0: any". The `error` got to be `Error` for Visual Studio Code to show correct typing for some reason after testing it. What I don't understand is I've read many posts and issues saying that types are not case sensitive when writing types. Lowercase should work with primitive types though after testing with `number` for JavaScript. Just thought I just note this in case others bump into same issue. – Nova Sep 27 '17 at 20:58
  • @Nova `Error` is not a primitive type? – yyny Sep 29 '17 at 12:36
  • Error is a non-primitive type I believe. – Nova Sep 30 '17 at 13:09
8

Workaround to make VSCode understand it

/**
 * @typedef {function(FpsInfo)} fpsCallback
 * @callback fpsCallback
 * @param {FpsInfo} fps Fps info object
 */

 /**
 * @typedef {Object} FpsInfo
 * @property {number} fps The calculated frames per second
 * @property {number} jitter The absolute difference since the last calculated fps
 * @property {number} elapsed Milliseconds ellapsed since the last computation
 * @property {number} frames Number of frames since the last computation
 * @property {number} trigger Next computation will happen at this amount of frames
 */

/**
 * FPS Meter - Returns a function that is used to compute the framerate without the overhead of updating the DOM every frame.
 * @param {fpsCallback} callback Callback fired every time the FPS is computed
 * @param {number} [refreshRate=1] Refresh rate which the fps is computed and the callback is fired (0 to compute every frame, not recommended)
 * @returns {function} Returns a function that should be called on every the loop tick
 * @author Victor B - www.vitim.us - github.com/victornpb/fpsMeter
 */
function createFpsMeter(callback, refreshRate = 1) {
    // ...
}

enter image description here enter image description here

Vitim.us
  • 16,145
  • 12
  • 81
  • 96
2

Possibly I'm arriving late to this answer...however this is my contribution. Using ES6 we can do this:

    /**
 *
 * @param {import('../clients')} clients  
 */
export default function socketServer(clients) {
io.on('connection', (webClient) => {


    webClient.on('register', (data) => {
      clients.add(data, webClient);
    });
}

 server.listen(8081, function (err) {
    if (err) throw err;
    console.log('listening on port 8081');
  });

)

In the folder 'clients' we have an index.js file with this code

let clients = new Map();

/**
 * Add Client to Collection
 * @param {string} key
 * @param {object} client
 */
export function add(key, client) {
  clients.set(key, client);
}
/**
 * Remove Client from Collection
 * @param {string} key
 */
export function remove(key) {
  clients.delete(key);
}

export const size = () => clients.size;

So, all functions that are being exported in file /clients/index.js are available as JsDOC and you can refer them via IntelliSense

2
@param {function(number):void}

Works well in VS Code and WebStorm as of 2021

code đờ
  • 95
  • 2
  • 9