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

Commit

Permalink
Fix EventEmitter.once issue and add EventEmitter removeAllListeners p…
Browse files Browse the repository at this point in the history
…atch. (#500)

* fix long-stack-trace zone will not render correctly when reject a promise

* fix issue #484 and #491, patch EventEmitter.once and removeAllListeners

* add event emitter once test case

* prependlistener should add listener at the beginning of the listener array.
add test cases for prepend listener and once.

* use newest fetch to test
  • Loading branch information
JiaLiPassion authored and mhevery committed Nov 16, 2016
1 parent 1f2ebc7 commit eff3353
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 15 deletions.
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');
});
});
});

0 comments on commit eff3353

Please sign in to comment.