Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Fix EventEmitter.once issue and add EventEmitter removeAllListeners patch. #500

Merged
merged 7 commits into from
Nov 16, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@
var gulp = require('gulp');
var rollup = require('gulp-rollup');
var rename = require("gulp-rename");
var rename = require('gulp-rename');
var uglify = require('gulp-uglify');
var pump = require('pump');
var uglify = require('gulp-uglify');
var path = require('path');
var spawn = require('child_process').spawn;

Expand Down
61 changes: 54 additions & 7 deletions lib/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,16 @@ const EVENT_TASKS = zoneSymbol('eventTasks');
const ADD_EVENT_LISTENER = 'addEventListener';
const REMOVE_EVENT_LISTENER = 'removeEventListener';

interface NestedEventListener {
listener?: EventListenerOrEventListenerObject;
}

declare type NestedEventListenerOrEventListenerObject = NestedEventListener | EventListener | EventListenerObject;

interface ListenerTaskMeta extends TaskData {
useCapturing: boolean;
eventName: string;
handler: EventListenerOrEventListenerObject;
handler: NestedEventListenerOrEventListenerObject;
target: any;
name: string;
}
Expand All @@ -134,7 +139,8 @@ function findExistingRegisteredTask(
for (let i = 0; i < eventTasks.length; i++) {
const eventTask = eventTasks[i];
const data = <ListenerTaskMeta>eventTask.data;
if (data.handler === handler && data.useCapturing === capture && data.eventName === name) {
const listener = <NestedEventListener>data.handler;
if ((data.handler === handler || listener.listener === handler) && data.useCapturing === capture && data.eventName === name) {
if (remove) {
eventTasks.splice(i, 1);
}
Expand All @@ -145,25 +151,47 @@ function findExistingRegisteredTask(
return null;
}

function findAllExistingRegisteredTasks(target: any, name: string, capture: boolean, remove: boolean): Task[] {
const eventTasks: Task[] = target[EVENT_TASKS];
if (eventTasks) {
const result = [];
for (var i = eventTasks.length - 1; i >= 0; i --) {
const eventTask = eventTasks[i];
const data = <ListenerTaskMeta>eventTask.data;
if (data.eventName === name && data.useCapturing === capture) {
result.push(eventTask);
if (remove) {
eventTasks.splice(i, 1);
}
}
}
return result;
}
return null;
}

function attachRegisteredEvent(target: any, eventTask: Task): void {
function attachRegisteredEvent(target: any, eventTask: Task, isPrepend: boolean): void {
let eventTasks: Task[] = target[EVENT_TASKS];
if (!eventTasks) {
eventTasks = target[EVENT_TASKS] = [];
}
eventTasks.push(eventTask);
if (isPrepend) {
eventTasks.unshift(eventTask);
} else {
eventTasks.push(eventTask);
}
}

export function makeZoneAwareAddListener(
addFnName: string, removeFnName: string, useCapturingParam: boolean = true,
allowDuplicates: boolean = false) {
allowDuplicates: boolean = false, isPrepend: boolean = false) {
const addFnSymbol = zoneSymbol(addFnName);
const removeFnSymbol = zoneSymbol(removeFnName);
const defaultUseCapturing = useCapturingParam ? false : undefined;

function scheduleEventListener(eventTask: Task): any {
const meta = <ListenerTaskMeta>eventTask.data;
attachRegisteredEvent(meta.target, eventTask);
attachRegisteredEvent(meta.target, eventTask, isPrepend);
return meta.target[addFnSymbol](meta.eventName, eventTask.invoke, meta.useCapturing);
}

Expand Down Expand Up @@ -247,6 +275,25 @@ export function makeZoneAwareRemoveListener(fnName: string, useCapturingParam: b
};
}

export function makeZoneAwareRemoveAllListeners(fnName: string, useCapturingParam: boolean = true) {
const symbol = zoneSymbol(fnName);
const defaultUseCapturing = useCapturingParam ? false : undefined;

return function zoneAwareRemoveAllListener(self: any, args: any[]) {
var eventName = args[0];
var useCapturing = args[1] || defaultUseCapturing;
var target = self || _global;
var eventTasks = findAllExistingRegisteredTasks(target, eventName, useCapturing, true);
if (eventTasks) {
for (var i = 0; i < eventTasks.length; i ++) {
var eventTask = eventTasks[i];
eventTask.zone.cancelTask(eventTask);
}
}
target[symbol](eventName, useCapturing);
}
}

export function makeZoneAwareListeners(fnName: string) {
const symbol = zoneSymbol(fnName);

Expand Down Expand Up @@ -371,4 +418,4 @@ export function patchMethod(
proto[name] = createNamedFn(name, patchFn(delegate, delegateName, name));
}
return delegate;
}
}
9 changes: 6 additions & 3 deletions lib/node/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {makeZoneAwareAddListener, makeZoneAwareListeners, makeZoneAwareRemoveListener, patchMethod} from '../common/utils';
import {makeZoneAwareAddListener, makeZoneAwareListeners, makeZoneAwareRemoveListener, makeZoneAwareRemoveAllListeners, patchMethod} from '../common/utils';

const callAndReturnFirstParam = (fn: (self: any, args: any[]) => any) => {
return (self: any, args: any[]) => {
Expand All @@ -19,19 +19,22 @@ const callAndReturnFirstParam = (fn: (self: any, args: any[]) => any) => {
const EE_ADD_LISTENER = 'addListener';
const EE_PREPEND_LISTENER = 'prependListener';
const EE_REMOVE_LISTENER = 'removeListener';
const EE_REMOVE_ALL_LISTENER = 'removeAllListeners';
const EE_LISTENERS = 'listeners';
const EE_ON = 'on';

const zoneAwareAddListener = callAndReturnFirstParam(makeZoneAwareAddListener(EE_ADD_LISTENER, EE_REMOVE_LISTENER, false, true));
const zoneAwarePrependListener = callAndReturnFirstParam(makeZoneAwareAddListener(EE_PREPEND_LISTENER, EE_REMOVE_LISTENER, false, true));
const zoneAwareAddListener = callAndReturnFirstParam(makeZoneAwareAddListener(EE_ADD_LISTENER, EE_REMOVE_LISTENER, false, true, false));
const zoneAwarePrependListener = callAndReturnFirstParam(makeZoneAwareAddListener(EE_PREPEND_LISTENER, EE_REMOVE_LISTENER, false, true, true));
const zoneAwareRemoveListener = callAndReturnFirstParam(makeZoneAwareRemoveListener(EE_REMOVE_LISTENER, false));
const zoneAwareRemoveAllListeners = callAndReturnFirstParam(makeZoneAwareRemoveAllListeners(EE_REMOVE_ALL_LISTENER, false));
const zoneAwareListeners = makeZoneAwareListeners(EE_LISTENERS);

export function patchEventEmitterMethods(obj: any): boolean {
if (obj && obj.addListener) {
patchMethod(obj, EE_ADD_LISTENER, () => zoneAwareAddListener);
patchMethod(obj, EE_PREPEND_LISTENER, () => zoneAwarePrependListener);
patchMethod(obj, EE_REMOVE_LISTENER, () => zoneAwareRemoveListener);
patchMethod(obj, EE_REMOVE_ALL_LISTENER, () => zoneAwareRemoveAllListeners);
patchMethod(obj, EE_LISTENERS, () => zoneAwareListeners);
obj[EE_ON] = obj[EE_ADD_LISTENER];
return true;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,6 @@
"ts-loader": "^0.6.0",
"tslint": "^3.15.1",
"typescript": "^2.0.2",
"whatwg-fetch": "^1.0.0"
"whatwg-fetch": "https://github.com/jimmywarting/fetch.git"
}
}
54 changes: 52 additions & 2 deletions test/node/events.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@
import {EventEmitter} from 'events';

describe('nodejs EventEmitter', () => {
let zone, zoneA, zoneB, emitter, expectZoneACount;
let zone, zoneA, zoneB, emitter, expectZoneACount, zoneResults;
beforeEach(() => {
zone = Zone.current;
zoneA = zone.fork({name: 'A'});
zoneB = zone.fork({name: 'B'});

emitter = new EventEmitter();
expectZoneACount = 0;

zoneResults = [];
});

function expectZoneA(value) {
Expand All @@ -25,6 +27,14 @@ describe('nodejs EventEmitter', () => {
expect(value).toBe('test value');
}

function listenerA() {
zoneResults.push('A');
}

function listenerB() {
zoneResults.push('B');
}

function shouldNotRun() {
fail('this listener should not run');
}
Expand Down Expand Up @@ -68,5 +78,45 @@ describe('nodejs EventEmitter', () => {
zoneA.run(() => {
expect(emitter.listeners('test')).toEqual([]);
});
})
});
it ('should prepend listener by order', () => {
zoneA.run(() => {
emitter.on('test', listenerA);
emitter.on('test', listenerB);
expect(emitter.listeners('test')).toEqual([listenerA, listenerB]);
emitter.emit('test');
expect(zoneResults).toEqual(['A', 'B']);
zoneResults = [];

emitter.removeAllListeners('test');

emitter.on('test', listenerA);
emitter.prependListener('test', listenerB);
expect(emitter.listeners('test')).toEqual([listenerB, listenerA]);
emitter.emit('test');
expect(zoneResults).toEqual(['B', 'A']);
});
});
it ('should remove All listeners properly', () => {
zoneA.run(() => {
emitter.on('test', expectZoneA);
emitter.on('test', expectZoneA);
emitter.removeAllListeners('test');
expect(emitter.listeners('test').length).toEqual(0);
});
});
it ('should remove once listener after emit', () => {
zoneA.run(() => {
emitter.once('test', expectZoneA);
emitter.emit('test', 'test value');
expect(emitter.listeners('test').length).toEqual(0);
});
});
it ('should remove once listener properly before listener triggered', () => {
zoneA.run(() => {
emitter.once('test', shouldNotRun);
emitter.removeListener('test', shouldNotRun);
emitter.emit('test');
});
});
});