Skip to content

Commit

Permalink
Extract platform specified features
Browse files Browse the repository at this point in the history
  • Loading branch information
vilicvane committed May 22, 2017
1 parent 6efb853 commit 514be35
Show file tree
Hide file tree
Showing 13 changed files with 205 additions and 176 deletions.
4 changes: 3 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/coverage/

/bld/test/
/bld/*/test/
/bld/local/
/src/
/test/
/typings/

/tsconfig.json
/tsconfig.*.json

.*
12 changes: 7 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
{
"name": "villa",
"version": "0.2.11",
"version": "0.3.0",
"description": "Promise utilities for async/await-ready environment.",
"main": "bld/index.js",
"typings": "bld/index.d.ts",
"main": "bld/edge/index.js",
"browser": "bld/es5/index.js",
"types": "bld/types/index.d.ts",
"scripts": {
"tsc": "tsc",
"clean": "rm -rf bld",
"build": "tsc",
"build:edge": "tsc -p tsconfig.edge.json",
"build:es5": "tsc -p tsconfig.es5.json",
"docheat": "docheat",
"docheat-travis": "docheat -b https://github.com/$TRAVIS_REPO_SLUG/blob/$TRAVIS_TAG/",
"quick-test": "mocha",
"bare-test": "mocha",
"test": "istanbul cover node_modules/mocha/bin/_mocha",
"coveralls": "cat coverage/lcov.info | coveralls"
},
Expand Down
1 change: 1 addition & 0 deletions platform/node.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import '../bld/types/node';
1 change: 1 addition & 0 deletions platform/node.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require('../bld/edge/node');
162 changes: 2 additions & 160 deletions src/awaitable.ts
Original file line number Diff line number Diff line change
@@ -1,153 +1,8 @@
import { ChildProcess } from 'child_process';
import { EventEmitter } from 'events';
import { Readable, Writable } from 'stream';

const {
EventEmitter: EventEmitterConstructor
} = safeRequire('events');

const {
ChildProcess: ChildProcessConstructor
} = safeRequire('child_process');

const {
Readable: ReadableConstructor,
Writable: WritableConstructor
} = safeRequire('stream');

type AwaitableCreator<T> = <T>(target: any, ...args: any[]) => Promise<T> | undefined;

let awaitableCreators = [] as AwaitableCreator<any>[];

export type EventEmitterResultAssertion<T> = (result: any) => T;

interface EventEmitterAwaitableOptions<T> {
types: string[];
assertion?: EventEmitterResultAssertion<T>;
}

function getEventEmitterAwaitableOptions(emitter: EventEmitter): EventEmitterAwaitableOptions<any> {
if (ChildProcessConstructor && emitter instanceof ChildProcessConstructor) {
return {
types: ['exit'],
assertion(code: number): void {
if (code !== 0) {
throw new Error(`Invalid exit code ${code}`);
}
}
};
} else if (
ReadableConstructor && emitter instanceof ReadableConstructor ||
WritableConstructor && emitter instanceof WritableConstructor
) {
return {
types: ['close']
};
} else {
throw new Error('Missing event types');
}
}

function eventEmitterAwaitableCreator<T>(emitter: EventEmitter, types: string | string[], errorEmitters?: EventEmitter[]): Promise<T> | undefined;
function eventEmitterAwaitableCreator<T>(emitter: EventEmitter, types: string | string[], assertion: EventEmitterResultAssertion<T>, errorEmitters?: EventEmitter[]): Promise<T> | undefined;
function eventEmitterAwaitableCreator(process: ChildProcess, errorEmitters?: EventEmitter[]): Promise<void>;
function eventEmitterAwaitableCreator(stream: Readable | Writable, errorEmitters?: EventEmitter[]): Promise<void>;
function eventEmitterAwaitableCreator<T>(
emitter: EventEmitter,
types: string | string[] | EventEmitter[] | undefined,
assertion: EventEmitterResultAssertion<T> | EventEmitter[] | undefined = [],
errorEmitters?: EventEmitter[] | undefined
): Promise<T> | undefined {
if (!(emitter instanceof EventEmitterConstructor)) {
return undefined;
}

if (typeof types === 'string' || isStringArray(types)) {
if (typeof types === 'string') {
types = [types];
}

if (Array.isArray(assertion)) {
errorEmitters = assertion;
// TODO: possibly a bug of TypeScript 2.2, adding ! temporarily.
assertion = undefined!;
}
} else {
errorEmitters = types as EventEmitter[] | undefined;

let options = getEventEmitterAwaitableOptions(emitter);

types = options.types;
// TODO: possibly a bug of TypeScript 2.2, adding ! temporarily.
assertion = options.assertion!;
}

if (!errorEmitters) {
errorEmitters = [];
}

errorEmitters.unshift(emitter);

let promise = new Promise<any>((resolve, reject) => {
for (let type of types as string[]) {
emitter.on(type, onsuccess);
}

for (let emitter of errorEmitters!) {
emitter.on('error', onerror);
}

function removeListeners() {
for (let type of types as string[]) {
emitter.removeListener(type, onsuccess);
}

for (let emitter of errorEmitters!) {
emitter.removeListener('error', onerror);
}
}

function onsuccess(value: T) {
removeListeners();
resolve(value);
}

function onerror(error: any) {
setImmediate(removeListeners);
reject(error);
}
});

if (assertion) {
promise = promise.then(assertion as EventEmitterResultAssertion<T>);
}

return promise;
}

/* istanbul ignore else */
if (EventEmitterConstructor) {
awaitableCreators.push(eventEmitterAwaitableCreator);
}
/** @internal */
export const awaitableCreators = [] as AwaitableCreator<any>[];

/**
* Create a promise for an event emitter.
*/
export function awaitable(emitter: EventEmitter, types: string | string[], errorEmitters?: EventEmitter[]): Promise<void>;
export function awaitable<T>(emitter: EventEmitter, types: string | string[], errorEmitters?: EventEmitter[]): Promise<T>;
export function awaitable<T>(emitter: EventEmitter, types: string | string[], assertion: EventEmitterResultAssertion<T>, errorEmitters?: EventEmitter[]): Promise<T>;
/**
* Create a promise for a `ChildProcess` object.
* @param process The process to listen on 'exit' and 'error' events for
* fulfillment or rejection.
*/
export function awaitable(process: ChildProcess, errorEmitters?: EventEmitter[]): Promise<void>;
/**
* Create a promise for a stream.
* @param stream The stream to listen on 'close' and 'error' events for
* fulfillment or rejection.
*/
export function awaitable(stream: Readable | Writable, errorEmitters?: EventEmitter[]): Promise<void>;
/**
* Create a promise for an event emitter.
* @param emitter The emitter to listen on 'error' event for rejection, and
Expand All @@ -167,16 +22,3 @@ export function awaitable<T>(target: any, ...args: any[]): Promise<T> {

throw new TypeError('Cannot create awaitable from the target object with given arguments');
}

function safeRequire(id: string): any {
try {
return require(id);
} catch (error) {
/* istanbul ignore next */
return {};
}
}

function isStringArray(object: any): object is string[] {
return Array.isArray(object) && typeof object[0] === 'string';
}
163 changes: 163 additions & 0 deletions src/node/awaitable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { ChildProcess } from 'child_process';
import { EventEmitter } from 'events';
import { Readable, Writable } from 'stream';

const {
EventEmitter: EventEmitterConstructor
} = safeRequire('events');

const {
ChildProcess: ChildProcessConstructor
} = safeRequire('child_process');

const {
Readable: ReadableConstructor,
Writable: WritableConstructor
} = safeRequire('stream');

import { awaitableCreators } from '../awaitable';

export type EventEmitterResultAssertion<T> = (result: any) => T;

interface EventEmitterAwaitableOptions<T> {
types: string[];
assertion?: EventEmitterResultAssertion<T>;
}

function getEventEmitterAwaitableOptions(emitter: EventEmitter): EventEmitterAwaitableOptions<any> {
if (ChildProcessConstructor && emitter instanceof ChildProcessConstructor) {
return {
types: ['exit'],
assertion(code: number): void {
if (code !== 0) {
throw new Error(`Invalid exit code ${code}`);
}
}
};
} else if (
ReadableConstructor && emitter instanceof ReadableConstructor ||
WritableConstructor && emitter instanceof WritableConstructor
) {
return {
types: ['close']
};
} else {
throw new Error('Missing event types');
}
}

function eventEmitterAwaitableCreator<T>(emitter: EventEmitter, types: string | string[], errorEmitters?: EventEmitter[]): Promise<T> | undefined;
function eventEmitterAwaitableCreator<T>(emitter: EventEmitter, types: string | string[], assertion: EventEmitterResultAssertion<T>, errorEmitters?: EventEmitter[]): Promise<T> | undefined;
function eventEmitterAwaitableCreator(process: ChildProcess, errorEmitters?: EventEmitter[]): Promise<void>;
function eventEmitterAwaitableCreator(stream: Readable | Writable, errorEmitters?: EventEmitter[]): Promise<void>;
function eventEmitterAwaitableCreator<T>(
emitter: EventEmitter,
types: string | string[] | EventEmitter[] | undefined,
assertion: EventEmitterResultAssertion<T> | EventEmitter[] | undefined = [],
errorEmitters?: EventEmitter[] | undefined
): Promise<T> | undefined {
if (!(emitter instanceof EventEmitterConstructor)) {
return undefined;
}

if (typeof types === 'string' || isStringArray(types)) {
if (typeof types === 'string') {
types = [types];
}

if (Array.isArray(assertion)) {
errorEmitters = assertion;
// TODO: possibly a bug of TypeScript 2.2, adding ! temporarily.
assertion = undefined!;
}
} else {
errorEmitters = types as EventEmitter[] | undefined;

let options = getEventEmitterAwaitableOptions(emitter);

types = options.types;
// TODO: possibly a bug of TypeScript 2.2, adding ! temporarily.
assertion = options.assertion!;
}

if (!errorEmitters) {
errorEmitters = [];
}

errorEmitters.unshift(emitter);

let promise = new Promise<any>((resolve, reject) => {
for (let type of types as string[]) {
emitter.on(type, onsuccess);
}

for (let emitter of errorEmitters!) {
emitter.on('error', onerror);
}

function removeListeners() {
for (let type of types as string[]) {
emitter.removeListener(type, onsuccess);
}

for (let emitter of errorEmitters!) {
emitter.removeListener('error', onerror);
}
}

function onsuccess(value: T) {
removeListeners();
resolve(value);
}

function onerror(error: any) {
setImmediate(removeListeners);
reject(error);
}
});

if (assertion) {
promise = promise.then(assertion as EventEmitterResultAssertion<T>);
}

return promise;
}

/* istanbul ignore else */
if (EventEmitterConstructor) {
awaitableCreators.push(eventEmitterAwaitableCreator);
}

function safeRequire(id: string): any {
try {
return require(id);
} catch (error) {
/* istanbul ignore next */
return {};
}
}

function isStringArray(object: any): object is string[] {
return Array.isArray(object) && typeof object[0] === 'string';
}

declare module '../index' {
/**
* Create a promise for an event emitter.
*/
function awaitable(emitter: EventEmitter, types: string | string[], errorEmitters?: EventEmitter[]): Promise<void>;
function awaitable<T>(emitter: EventEmitter, types: string | string[], errorEmitters?: EventEmitter[]): Promise<T>;
function awaitable<T>(emitter: EventEmitter, types: string | string[], assertion: EventEmitterResultAssertion<T>, errorEmitters?: EventEmitter[]): Promise<T>;
/**
* Create a promise for a `ChildProcess` object.
* @param process The process to listen on 'exit' and 'error' events for
* fulfillment or rejection.
*/
function awaitable(process: ChildProcess, errorEmitters?: EventEmitter[]): Promise<void>;
/**
* Create a promise for a stream.
* @param stream The stream to listen on 'close' and 'error' events for
* fulfillment or rejection.
*/
function awaitable(stream: Readable | Writable, errorEmitters?: EventEmitter[]): Promise<void>;
}
1 change: 1 addition & 0 deletions src/node/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import './awaitable';
2 changes: 2 additions & 0 deletions src/test/awaitable/awaitable-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { expect } from 'chai';

import { awaitable } from '../../';

import '../../node';

let testError = new Error();

let testValue = {
Expand Down
2 changes: 1 addition & 1 deletion src/test/concurrency/race-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('Feature: race', () => {
value.should.equal(values[index]);
throw error;
} else {
return new Promise(resolve => setTimeout(resolve));
return new Promise<void>(resolve => setTimeout(resolve, 0));
}
});

Expand Down

0 comments on commit 514be35

Please sign in to comment.