Skip to content

Commit

Permalink
New: Add TypeScript definitions (closes #41) (#47)
Browse files Browse the repository at this point in the history
  • Loading branch information
demurgos authored and phated committed May 13, 2018
1 parent bc08f06 commit d79bddf
Show file tree
Hide file tree
Showing 11 changed files with 348 additions and 9 deletions.
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ Optionally takes a callback to call when async tasks are complete.
* `Promise` returned
- Completion: [onFulfilled][promise-onfulfilled] method called
- Error: [onRejected][promise-onrejected] method called
* `Observable` returned
- Completion: [onCompleted][observable-subscribe] method called
- Error: [onError][observable-subscribe] method called
* `Observable` (e.g. from [RxJS v5][rxjs5-observable] or [RxJS v4][rxjs5-observable]) returned
- Completion: [complete][rxjs5-subscriber-complete] method called
- Error: [error][rxjs5-subscriber-error] method called

__Warning:__ Sync tasks are __not supported__ and your function will never complete if the one of the above strategies is not used to signal completion. However, thrown errors will be caught by the domain.

Expand All @@ -96,7 +96,10 @@ MIT
[event-stream]: https://github.com/dominictarr/event-stream
[promise-onfulfilled]: http://promisesaplus.com/#point-26
[promise-onrejected]: http://promisesaplus.com/#point-30
[observable-subscribe]: https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/subscribe.md
[rx4-observable]: https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md
[rxjs5-observable]: http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html
[rxjs5-observer-complete]: http://reactivex.io/rxjs/class/es6/MiscJSDoc.js~ObserverDoc.html#instance-method-complete
[rxjs5-observer-error]: http://reactivex.io/rxjs/class/es6/MiscJSDoc.js~ObserverDoc.html#instance-method-error

[downloads-image]: http://img.shields.io/npm/dm/async-done.svg
[npm-url]: https://www.npmjs.com/package/async-done
Expand Down
101 changes: 101 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* Notes about these type definitions:
*
* - Callbacks returning multiple completion values using multiple arguments are not supported by these types.
* Prefer to use Node's style by grouping your values in a single object or array.
* Support for this kind of callback is blocked by Microsoft/TypeScript#5453
*
* - For ease of use, `asyncDone` lets you pass callback functions with a result type `T` instead of `T | undefined`.
* This matches Node's types but can lead to unsound code being typechecked.
*
* The following code typechecks but fails at runtime:
* ```typescript
* async function getString(): Promise<string> {
* return "Hello, World!";
* }
*
* async function evilGetString(): Promise<string> {
* throw new Error("Hello, World!");
* }
*
* function cb(err: Error | null, result: string): void {
* // This is unsound because `result` is `undefined` when `err` is not `null`.
* console.log(result.toLowerCase());
* }
*
* asyncDone(getString, cb); // Prints `hello, world!`
* asyncDone(evilGetString, cb); // Runtime error: `TypeError: Cannot read property 'toLowerCase' of undefined`
* ```
*
* Enforcing stricter callbacks would require developers to use `result?: string` and assert the existence
* of the result either by checking it directly or using the `!` assertion operator after testing for errors.
* ```typescript
* function stricterCb1(err: Error | null, result?: string): void {
* if (err !== null) {
* console.error(err);
* return;
* }
* console.log(result!.toLowerCase());
* }
*
* function stricterCb2(err: Error | null, result?: string): void {
* if (result === undefined) {
* console.error("Undefined result. Error:);
* console.error(err);
* return;
* }
* console.log(result.toLowerCase());
* }
* ```
*/
import { ChildProcess } from "child_process";
import { EventEmitter } from "events";
import { Stream } from "stream";

declare namespace asyncDone {

/**
* Represents a callback function used to signal the completion of a
* task without any result value.
*/
type VoidCallback = (err: Error | null) => void;

/**
* Represents a callback function used to signal the completion of a
* task with a single result value.
*/
interface Callback<T> {
(err: null, result: T): void;

// Use `result?: T` or `result: undefined` to require the consumer to assert the existence of the result
// (even in case of success). See comment at the top of the file.
(err: Error, result?: any): void;
}

/**
* Minimal `Observable` interface compatible with `async-done`.
*
* @see https://github.com/ReactiveX/rxjs/blob/c3c56867eaf93f302ac7cd588034c7d8712f2834/src/internal/Observable.ts#L77
*/
interface Observable<T = any> {
subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): any;
}

/**
* Represents an async operation.
*/
export type AsyncTask<R = any> =
((done: VoidCallback) => void)
| ((done: Callback<R>) => void)
| (() => ChildProcess | EventEmitter | Observable<R> | PromiseLike<R> | Stream);
}

/**
* Takes a function to execute (`fn`) and a function to call on completion (`callback`).
*
* @param fn Function to execute.
* @param callback Function to call on completion.
*/
declare function asyncDone<R = any>(fn: asyncDone.AsyncTask<R>, callback: asyncDone.Callback<R>): void;

export = asyncDone;
11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,25 @@
"contributors": [
"Blaine Bublitz <blaine.bublitz@gmail.com>",
"Pawel Kozlowski <pkozlowski.opensource@gmail.com>",
"Matthew Podwysocki <matthew.podwysocki@gmail.com>"
"Matthew Podwysocki <matthew.podwysocki@gmail.com>",
"Charles Samborski <demurgos@demurgos.net>"
],
"repository": "gulpjs/async-done",
"license": "MIT",
"engines": {
"node": ">= 0.10"
},
"main": "index.js",
"types": "index.d.ts",
"files": [
"index.js",
"LICENSE"
],
"scripts": {
"lint": "eslint . && jscs index.js test/",
"pretest": "npm run lint",
"test": "mocha --async-only",
"test": "mocha --async-only && npm run test-types",
"test-types": "tsc -p test/types",
"cover": "istanbul cover _mocha --report lcovonly",
"coveralls": "npm run cover && istanbul-coveralls"
},
Expand All @@ -32,6 +35,7 @@
"stream-exhaust": "^1.0.1"
},
"devDependencies": {
"@types/node": "^9.3.0",
"eslint": "^1.7.3",
"eslint-config-gulp": "^2.0.0",
"expect": "^1.19.0",
Expand All @@ -41,8 +45,9 @@
"jscs-preset-gulp": "^1.0.0",
"mocha": "^2.4.5",
"pumpify": "^1.3.6",
"rx": "^4.0.6",
"rxjs": "^5.5.6",
"through2": "^2.0.0",
"typescript": "^2.6.2",
"when": "^3.7.3"
},
"keywords": [
Expand Down
5 changes: 3 additions & 2 deletions test/observables.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ var expect = require('expect');

var asyncDone = require('../');

var Observable = require('rx').Observable;
var Observable = require('rxjs').Observable;

function success() {
return Observable.empty();
}

function successValue() {
return Observable.return(42);
// This corresponds to `Observable.return(42);` in RxJS 4
return Observable.of(42);
}

function failure() {
Expand Down
44 changes: 44 additions & 0 deletions test/types/callback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import asyncDone, { Callback } from "async-done";

function success(cb: Callback<number>): void {
cb(null, 2);
}

function failure(cb: Callback<number>): void {
cb(new Error("Callback Error"));
}

function neverDone(): number {
return 2;
}

// `success` and stricter callback
asyncDone(success, function (err: Error | null, result?: number): void {
console.log("Done");
});

// The following code fails to compile as expected:
// asyncDone(success, function (err: Error | null, result?: string): void {
// console.log("Done");
// });

// `success` and unsound callback
asyncDone(success, function (err: Error | null, result: number): void {
console.log("Done");
});

// `failure` and stricter callback
asyncDone(failure, function (err: Error | null, result?: number): void {
console.log("Done");
});

// `failure` and unsound callback
asyncDone(failure, function (err: Error | null, result: number): void {
console.log("Done");
});

// I don't think TS is currently able to prevent the current code from compiling
// (`neverDone` matches with `(done: VoidCallback) => void` for example)
// asyncDone(neverDone, function(err: Error | null, result?: number): void {
// console.log("Done");
// });
19 changes: 19 additions & 0 deletions test/types/child_processes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import asyncDone from "async-done";
import cp from "child_process";


function success(): cp.ChildProcess {
return cp.exec("echo hello world");
}

function failure(): cp.ChildProcess {
return cp.exec("foo-bar-baz hello world");
}

asyncDone(success, function (err: Error | null): void {
console.log("Done");
});

asyncDone(failure, function (err: Error | null): void {
console.log("Done");
});
46 changes: 46 additions & 0 deletions test/types/observables.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import asyncDone from "async-done";
import { Observable } from "rxjs/Observable";
import 'rxjs/add/observable/empty';
import 'rxjs/add/observable/of';

function success(): Observable<undefined> {
return Observable.empty();
}

function successValue(): Observable<number> {
return Observable.of(42);
}

function failure(): Observable<number> {
return Observable.throw(new Error("Observable error"));
}

// `success` callback
asyncDone(success, function (err: Error | null): void {
console.log("Done");
});

// The following code fails to compile as expected (`undefined` is not assignable to `number`):
// asyncDone(success, function (err: Error | null, result: number): void {
// console.log("Done");
// });

// `successValue` and stricter callback
asyncDone(successValue, function (err: Error | null, result?: number): void {
console.log("Done");
});

// `successValue` and unsound callback
asyncDone(successValue, function (err: Error | null, result: number): void {
console.log("Done");
});

// `failure` and stricter callback
asyncDone(failure, function (err: Error | null, result?: number): void {
console.log("Done");
});

// `failure` and unsound callback
asyncDone(failure, function (err: Error | null, result: number): void {
console.log("Done");
});
34 changes: 34 additions & 0 deletions test/types/promises.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import asyncDone from "async-done";

function success(): Promise<number> {
return Promise.resolve(2);
}

function failure(): Promise<number> {
return Promise.reject(new Error("Promise Error"));
}

// `successValue` and stricter callback
asyncDone(success, function (err: Error | null, result?: number): void {
console.log("Done");
});

// The following code fails to compile as expected:
// asyncDone(success, function (err: Error | null, result?: string): void {
// console.log("Done");
// });

// `successValue` and unsound callback
asyncDone(success, function (err: Error | null, result: number): void {
console.log("Done");
});

// `failure` and stricter callback
asyncDone(failure, function (err: Error | null, result?: number): void {
console.log("Done");
});

// `failure` and unsound callback
asyncDone(failure, function (err: Error | null, result: number): void {
console.log("Done");
});
18 changes: 18 additions & 0 deletions test/types/streams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import asyncDone from "async-done";
import { Readable, Stream } from "stream";

function streamSuccess(): Stream {
return new Stream();
}

function streamFail(): Stream {
return new Stream();
}

asyncDone(streamSuccess, function (err: Error | null): void {
console.log("Done");
});

asyncDone(streamFail, function (err: Error | null): void {
console.log("Done");
});
Loading

0 comments on commit d79bddf

Please sign in to comment.