/cps1

A specification for continuation-passing style functions and callbacks in JavaScript.

Callback-Passing Style 1 (CPS1)

Open Standard Draft

Callback-passing style (CPS) is the predominant way of handling asynchronous operations in JavaScript, especially in Node.js. CPS functions expect one or more callbacks, or continuations, that continue the program flow after certain events. Promises moved in and out of the Node.js core, and eventually got specified with Promises/A+, but even after Node.js went back to CPS, the style used in Node.js was never specified.

The CPS specified in this document is named CPS1 because it was designed to match Node.js which widely uses a single callback, error-first CPS.

The goal is to provide a common contract on CPS for developers and users of asynchronous code. Compliance is especially desired in meta-programming, where, for example, promises can be generated automatically from CPS1 functions, relying on a common signature.

1. Preliminaries

  • 1.1. A function is any JavaScript function.
  • 1.2. A value is any JavaScript value, including functions.
  • 1.3. A value is truthy if it is considered true in JavaScript.
  • 1.4. A value is falsy if it is not truthy.
  • 1.5. A function call is the evaluation of a function by any means, including but not limited to f(...), call, and apply.
  • 1.6. An argument is an entry in the arguments list of a function call.
  • 1.7. In a function call, an argument is the semantically last argument if all following arguments are ignored, i.e. neither presence nor value have effect on the function call.

2. CPS1 Operations

A CPS1 operation is a hereby specified sequence of events and actions:

  • 2.1. A CPS1 operation must be started by a function call.
  • 2.2. The operation must reserve the semantically last argument of the function call for a callback.
  • 2.3. If the callback is not a function, the operation may use a default callback instead, or should throw an argument error indicating a missing callback immediately.
  • 2.4. The operation should not throw any exceptions besides argument errors in 2.3. Instead, exceptions should be caught and passed to the callback (see section E).
  • 2.5. The operation must eventually complete with one of the Error, Success, or Uncaught Exception actions, defined by sections E, S, and U.
  • 2.6. In any case, the callback must be called at most once by the operation.
  • 2.7. The operation must not take any actions after completion.

E. Error

  • E1. The callback is called with an error as its first argument, immediately or at any time.
  • E2. The error argument must be truthy.
  • E3. The error argument should be an instance of a JavaScript error class, e.g. Error.
  • E4. Additional arguments may be passed to the callback. They are ignored by CPS1 callbacks.

S. Success

  • S1. The callback is called with a falsy first argument and result arguments, if any, immediately or at any time.
  • S2. The result(s) of the operation may be passed to the callback as additional arguments.
  • S3. If no results need to be passed, the callback may be called with an empty argument list.
  • S4. The number of result arguments must be obvious, and should be constant for equivalent contexts.
  • S5. In particular, if the last result argument is intended to be undefined, it must be explicitly passed to the callback.

U. Uncaught Exception

  • U1. During the operation, an exception is thrown and not caught by the operation, immediately or at any time.
  • U2. Uncaught exceptions should be avoided (see 2.4).

3. CPS1 callbacks

A CPS1 callback is a function which

  • 3.1. must reserve the first argument for an error.
  • 3.2. must ignore any other arguments if the error argument is truthy.
  • 3.3. should not throw any exceptions.
  • 3.4. should return undefined.

4. Notes

  • 4.1. Because functions may have multiple signatures and program flows, this specification defines abstract CPS1 operations, not functions. This way, function calls may or may not execute a CPS1 operation, depending on arguments or context (that is, anything that may effect a function call, like the current program state and environment).
  • 4.2. Functions designed to be CPS1-compliant should always start a CPS1 operation when called. If not, it must be obvious for which arguments and context a function call starts a CPS1 operation. This may be achieved through documentation, comments, or function signatures.

5. Examples

// Example 5.1
// setTimeout is not CPS1
// Violates 2.2: Callback is not the (semantically) last argument.
// function setTimeout( callback, t )

// CPS1
function setTimeoutCPS1( t, callback ) {

	return setTimeout( callback, t );

}

// Example 5.2
// func is not CPS1
// Violates S5: Must explicitly pass undefined as a result.
function func( one, two, callback ) {

	if ( !two ) callback( null, one );
	else callback( null, one, two );

}

// CPS1
function funcCPS1( one, two, callback ) {

	if ( !two ) callback( null, one, undefined );
	else callback( null, one, two );

}

// Example 5.3
// ready is not a CPS1 callback
// Violates 3.2.: Must ignore data on error
load( function ready( err, data ) {

	if ( err ) console.warn( err );
	console.log( data );

} );

// CPS1
load( function readyCPS1( err, data ) {

	if ( err ) console.warn( err );
	else console.log( data );

} );

6. Status and Contributing

This is a draft of an open standard that is hoped to help the community. Please feel free to contribute by creating issues or pull requests, or by sending any feedback or ideas.

Contributors:

7. License

This work is dedicated to the public domain.

https://creativecommons.org/publicdomain/zero/1.0/

8. Acknowledgments