Skip to content

Commit d0c940b

Browse files
committed
feat(core): Ensure concurrency when firing events
1 parent 3015f4c commit d0c940b

File tree

3 files changed

+66
-26
lines changed

3 files changed

+66
-26
lines changed

packages/core/data/observable/index.ts

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ interface ListenerEntry {
4242
once?: true;
4343
}
4444

45+
interface ListEntryMap {
46+
[eventName: string]: Array<ListenerEntry>;
47+
}
48+
4549
let _wrappedIndex = 0;
4650

4751
/**
@@ -107,7 +111,7 @@ export class Observable {
107111
*/
108112
public _isViewBase: boolean;
109113

110-
private readonly _observers: { [eventName: string]: Array<ListenerEntry> } = {};
114+
private readonly _observers: ListEntryMap = {};
111115

112116
/**
113117
* Gets the value of the specified property.
@@ -379,7 +383,7 @@ export class Observable {
379383
const eventName = data.eventName + eventType;
380384
const entries = _globalEventHandlers[eventClass][eventName];
381385
if (entries) {
382-
Observable._handleEvent(entries, data);
386+
Observable._fireEvent(entries, data);
383387
}
384388
}
385389

@@ -388,7 +392,7 @@ export class Observable {
388392
const eventName = data.eventName + eventType;
389393
const entries = _globalEventHandlers['*'][eventName];
390394
if (entries) {
391-
Observable._handleEvent(entries, data);
395+
Observable._fireEvent(entries, data);
392396
}
393397
}
394398
}
@@ -411,35 +415,49 @@ export class Observable {
411415

412416
const observers = this._observers[data.eventName];
413417
if (observers) {
414-
Observable._handleEvent(observers, dataWithObject);
418+
Observable._fireEvent(observers, dataWithObject);
415419
}
416420

417421
this._globalNotify(eventClass, '', dataWithObject);
418422
}
419423

420-
private static _handleEvent<T extends EventData>(observers: Array<ListenerEntry>, data: T): void {
421-
if (!observers.length) {
424+
private static _fireEvent<T extends EventData>(observers: Array<ListenerEntry>, data: T): void {
425+
const length = observers.length;
426+
427+
if (!length) {
422428
return;
423429
}
424430

425-
for (let i = observers.length - 1; i >= 0; i--) {
426-
const entry = observers[i];
427-
if (!entry) {
428-
continue;
429-
}
431+
if (length === 1) {
432+
const index = 0;
433+
this._handleListenerEntry<T>(observers[index], index, observers, data);
434+
} else {
435+
// This keeps a copy of observers list to ensure concurrency
436+
const observersCp = observers.slice();
430437

431-
if (entry.once) {
432-
observers.splice(i, 1);
438+
for (let i = 0; i < length; i++) {
439+
const entry = observersCp[i];
440+
this._handleListenerEntry<T>(entry, i, observers, data);
433441
}
442+
}
443+
}
434444

435-
const returnValue = entry.thisArg ? entry.callback.apply(entry.thisArg, [data]) : entry.callback(data);
445+
private static _handleListenerEntry<T extends EventData>(entry: ListenerEntry, index: number, observers: Array<ListenerEntry>, data: T): void {
446+
if (!entry) {
447+
return;
448+
}
436449

437-
// This ensures errors thrown inside asynchronous functions do not get swallowed
438-
if (returnValue instanceof Promise) {
439-
returnValue.catch((err) => {
440-
console.error(err);
441-
});
442-
}
450+
if (entry.once) {
451+
observers.splice(index, 1);
452+
}
453+
454+
const returnValue = entry.thisArg ? entry.callback.apply(entry.thisArg, [data]) : entry.callback(data);
455+
456+
// This ensures errors thrown inside asynchronous functions do not get swallowed
457+
if (returnValue instanceof Promise) {
458+
returnValue.catch((err) => {
459+
console.error(err);
460+
});
443461
}
444462
}
445463

packages/core/ui/core/view/index.android.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -694,12 +694,34 @@ export class View extends ViewCommon {
694694
}
695695

696696
public handleGestureTouch(event: android.view.MotionEvent): any {
697-
for (const type in this._gestureObservers) {
698-
const list = this._gestureObservers[type];
699-
list.forEach((element) => {
700-
element.androidOnTouchEvent(event);
701-
});
697+
// This keeps a copy of gesture observers from the map to ensure concurrency
698+
const allObservers = Object.values(this._gestureObservers);
699+
700+
for (const observers of allObservers) {
701+
const length = observers.length;
702+
703+
if (!length) {
704+
continue;
705+
}
706+
707+
if (length === 1) {
708+
const entry = observers[0];
709+
if (entry) {
710+
entry.androidOnTouchEvent(event);
711+
}
712+
} else {
713+
// This keeps a copy of gesture observers list to ensure concurrency
714+
const observersCp = observers.slice();
715+
716+
for (let i = 0; i < length; i++) {
717+
const entry = observersCp[i];
718+
if (entry) {
719+
entry.androidOnTouchEvent(event);
720+
}
721+
}
722+
}
702723
}
724+
703725
if (this.parent instanceof View) {
704726
this.parent.handleGestureTouch(event);
705727
}

packages/core/ui/gestures/index.android.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ function _getPanArgs(deltaX: number, deltaY: number, view: View, state: GestureS
441441

442442
function _executeCallback(observer: GesturesObserver, args: GestureEventData) {
443443
if (observer && observer.callback) {
444-
observer.callback.call((<any>observer)._context, args);
444+
observer.callback.call(observer.context, args);
445445
}
446446
}
447447

0 commit comments

Comments
 (0)