diff --git a/types/p-event/index.d.ts b/types/p-event/index.d.ts new file mode 100644 index 0000000000..6f76b75748 --- /dev/null +++ b/types/p-event/index.d.ts @@ -0,0 +1,205 @@ +// Type definitions for p-event 2.3 +// Project: https://github.com/sindresorhus/p-event#readme +// Definitions by: BendingBender +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.3 + +import { PCancelable } from 'p-cancelable'; + +export = pEvent; + +/** + * Promisify an event by waiting for it to be emitted. + * + * Returns a `Promise` that is fulfilled when emitter emits an event matching `event`, or rejects if emitter emits + * any of the events defined in the `rejectionEvents` option. + * + * **Note**: `event` is a string for a single event type, for example, `'data'`. To listen on multiple + * events, pass an array of strings, such as `['started', 'stopped']`. + * + * The returned promise has a `.cancel()` method, which when called, removes the event listeners and causes the promise to never be settled. + * + * @param emitter Event emitter object. Should have either a `.on()`/`.addListener()`/`.addEventListener()` and + * `.off()`/`.removeListener()`/`.removeEventListener()` method, like the [Node.js `EventEmitter`](https://nodejs.org/api/events.html) and + * [DOM events](https://developer.mozilla.org/en-US/docs/Web/Events). + * @param event Name of the event or events to listen to. If the same event is defined both here and in + * `rejectionEvents`, this one takes priority. + */ +declare function pEvent( + emitter: pEvent.Emitter, + event: string | symbol | Array, + options: pEvent.MultiArgsOptions +): PCancelable>; +declare function pEvent( + emitter: pEvent.Emitter, + event: string | symbol | Array, + filter: pEvent.FilterFn +): PCancelable; +declare function pEvent( + emitter: pEvent.Emitter, + event: string | symbol | Array, + options?: pEvent.Options +): PCancelable; + +declare namespace pEvent { + /** + * Wait for multiple event emissions. Returns an array. + */ + function multiple( + emitter: Emitter, + event: string | symbol | Array, + options: MultipleMultiArgsOptions + ): PCancelable>>; + function multiple( + emitter: Emitter, + event: string | symbol | Array, + options: MultipleOptions + ): PCancelable; + + /** + * Returns an [async iterator](http://2ality.com/2016/10/asynchronous-iteration.html) that lets you asynchronously + * iterate over events of `event` emitted from `emitter`. The iterator ends when `emitter` emits an event matching + * any of the events defined in `resolutionEvents`, or rejects if `emitter` emits any of the events defined in + * the `rejectionEvents` option. + */ + function iterator( + emitter: Emitter, + event: string | symbol | Array, + options: IteratorMultiArgsOptions + ): AsyncIterableIterator>; + function iterator( + emitter: Emitter, + event: string | symbol | Array, + filter: FilterFn + ): AsyncIterableIterator; + function iterator( + emitter: Emitter, + event: string | symbol | Array, + options?: IteratorOptions + ): AsyncIterableIterator; + + interface Emitter { + on?: AddRmListenerFn; + addListener?: AddRmListenerFn; + addEventListener?: AddRmListenerFn; + off?: AddRmListenerFn; + removeListener?: AddRmListenerFn; + removeEventListener?: AddRmListenerFn; + } + + type FilterFn = (el: T) => boolean; + + interface Options { + /** + * Events that will reject the promise. + * @default ['error'] + */ + rejectionEvents?: Array; + /** + * By default, the promisified function will only return the first argument from the event callback, + * which works fine for most APIs. This option can be useful for APIs that return multiple arguments + * in the callback. Turning this on will make it return an array of all arguments from the callback, + * instead of just the first argument. This also applies to rejections. + * + * @example + * const pEvent = require('p-event'); + * const emitter = require('./some-event-emitter'); + * + * (async () => { + * const [foo, bar] = await pEvent(emitter, 'finish', {multiArgs: true}); + * })(); + * + * @default false + */ + multiArgs?: boolean; + /** + * Time in milliseconds before timing out. + * @default Infinity + */ + timeout?: number; + /** + * Filter function for accepting an event. + * + * @example + * const pEvent = require('p-event'); + * const emitter = require('./some-event-emitter'); + * + * (async () => { + * const result = await pEvent(emitter, '🦄', value => value > 3); + * // Do something with first 🦄 event with a value greater than 3 + * })(); + */ + filter?: FilterFn; + } + + interface MultiArgsOptions extends Options { + multiArgs: true; + } + + interface MultipleOptions extends Options { + /** + * The number of times the event needs to be emitted before the promise resolves. + */ + count: number; + /** + * Whether to resolve the promise immediately. Emitting one of the `rejectionEvents` won't throw an error. + * + * **Note**: The returned array will be mutated when an event is emitted. + * + * @example + * const emitter = new EventEmitter(); + * + * const promise = pEvent.multiple(emitter, 'hello', { + * resolveImmediately: true, + * count: Infinity + * }); + * + * const result = await promise; + * console.log(result); + * //=> [] + * + * emitter.emit('hello', 'Jack'); + * console.log(result); + * //=> ['Jack'] + * + * emitter.emit('hello', 'Mark'); + * console.log(result); + * //=> ['Jack', 'Mark'] + * + * // Stops listening + * emitter.emit('error', new Error('😿')); + * + * emitter.emit('hello', 'John'); + * console.log(result); + * //=> ['Jack', 'Mark'] + */ + resolveImmediately?: boolean; + } + + interface MultipleMultiArgsOptions extends MultipleOptions { + multiArgs: true; + } + + interface IteratorOptions extends Options { + /** + * Maximum number of events for the iterator before it ends. When the limit is reached, the iterator will be + * marked as `done`. This option is useful to paginate events, for example, fetching 10 events per page. + * @default Infinity + */ + limit?: number; + /** + * Events that will end the iterator. + * @default [] + */ + resolutionEvents?: Array; + } + + interface IteratorMultiArgsOptions extends IteratorOptions { + multiArgs: true; + } +} + +type AddRmListenerFn = ( + event: string | symbol, + listener: (arg1: T, ...args: TRest[]) => void +) => void; diff --git a/types/p-event/p-event-tests.ts b/types/p-event/p-event-tests.ts new file mode 100644 index 0000000000..dbbe50eee7 --- /dev/null +++ b/types/p-event/p-event-tests.ts @@ -0,0 +1,110 @@ +/// + +import pEvent = require('p-event'); +import { EventEmitter } from 'events'; +import * as fs from 'fs'; + +class NodeEmitter extends EventEmitter { + on(event: 'finish', listener: (num: number, str: string) => void) { + return this; + } + addListener(event: 'finish', listener: (num: number, str: string) => void) { + return this; + } + addEventListener(event: 'finish', listener: (num: number, str: string) => void) { + return this; + } + off(event: 'finish', listener: (num: number, str: string) => void) { + return this; + } + removeListener(event: 'finish', listener: (num: number, str: string) => void) { + return this; + } + removeEventListener(event: 'finish', listener: (num: number, str: string) => void) { + return this; + } +} + +class DomEmitter implements EventTarget { + addEventListener( + type: 'foo', + listener: EventListenerOrEventListenerObject, + options?: boolean | AddEventListenerOptions + ): void {} + + dispatchEvent(event: Event): boolean { + return false; + } + + removeEventListener( + type: 'foo', + listener: EventListenerOrEventListenerObject, + options?: boolean | AddEventListenerOptions + ): void {} +} + +pEvent(new NodeEmitter(), 'finish'); // $ExpectType PCancelable +pEvent(new NodeEmitter(), '🦄', value => value > 3); // $ExpectType PCancelable +pEvent(new DomEmitter(), 'finish'); // $ExpectType PCancelable +pEvent(document, 'DOMContentLoaded'); // $ExpectType PCancelable + +pEvent(new NodeEmitter(), 'finish', { rejectionEvents: ['error'] }); // $ExpectType PCancelable +pEvent(new NodeEmitter(), 'finish', { timeout: 1 }); // $ExpectType PCancelable +pEvent(new NodeEmitter(), 'finish', { filter: value => value > 3 }); // $ExpectType PCancelable +pEvent(new NodeEmitter(), 'finish', { multiArgs: true }); // $ExpectType PCancelable<(string | number)[]> + +pEvent(new NodeEmitter(), 'finish').cancel(); + +// $ExpectType PCancelable +pEvent.multiple(new NodeEmitter(), 'hello', { + count: Infinity, +}); +// $ExpectType PCancelable +pEvent.multiple(new NodeEmitter(), 'hello', { + resolveImmediately: true, + count: Infinity, +}); +// $ExpectType PCancelable<(string | number)[][]> +pEvent.multiple(new NodeEmitter(), 'hello', { + count: Infinity, + multiArgs: true, +}); +// $ExpectError +pEvent.multiple(new NodeEmitter(), 'hello', {}); +// $ExpectError +pEvent.multiple(new NodeEmitter(), 'hello'); + +pEvent.iterator(new NodeEmitter(), 'finish'); // $ExpectType AsyncIterableIterator +pEvent.iterator(new NodeEmitter(), '🦄', value => value > 3); // $ExpectType AsyncIterableIterator + +pEvent.iterator(new NodeEmitter(), 'finish', { limit: 1 }); // $ExpectType AsyncIterableIterator +pEvent.iterator(new NodeEmitter(), 'finish', { resolutionEvents: ['finish'] }); // $ExpectType AsyncIterableIterator +pEvent.iterator(new NodeEmitter(), 'finish', { multiArgs: true }); // $ExpectType AsyncIterableIterator<(string | number)[]> + +async function getOpenReadStream(file: string) { + const stream = fs.createReadStream(file); + await pEvent(stream, 'open'); + return stream; +} + +(async () => { + const stream = await getOpenReadStream('unicorn.txt'); + stream.pipe(process.stdout); +})().catch(console.error); + +(async () => { + try { + const result = await pEvent(new NodeEmitter(), 'finish'); + + if (result === 1) { + throw new Error('Emitter finished with an error'); + } + + // `emitter` emitted a `finish` event with an acceptable value + console.log(result); + } catch (error) { + // `emitter` emitted an `error` event or + // emitted a `finish` with 'unwanted result' + console.error(error); + } +})(); diff --git a/types/p-event/tsconfig.json b/types/p-event/tsconfig.json new file mode 100644 index 0000000000..dfde34c0e1 --- /dev/null +++ b/types/p-event/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es2016", + "dom" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "strictFunctionTypes": false, + "baseUrl": "../", + "typeRoots": [ + "../" + ], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts", + "p-event-tests.ts" + ] +} diff --git a/types/p-event/tslint.json b/types/p-event/tslint.json new file mode 100644 index 0000000000..3db14f85ea --- /dev/null +++ b/types/p-event/tslint.json @@ -0,0 +1 @@ +{ "extends": "dtslint/dt.json" }