Improved EventEmitter for tauri api shell (#4697)

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
This commit is contained in:
Danil Karpenko 2022-07-26 00:35:35 +02:00 committed by GitHub
parent f7ea867d31
commit aa9f1243e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 184 additions and 73 deletions

View File

@ -0,0 +1,5 @@
---
"api": minor
---
Improve shell's `Command`, `Command.stdout` and `Command.stderr` events with new `once`, `off`, `listenerCount`, `prependListener`, `prependOnceListener` and `removeAllListeners` functions.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -93,9 +93,7 @@
svelte-hmr "^0.14.12"
"@tauri-apps/api@../../tooling/api/dist":
version "1.0.1"
dependencies:
type-fest "2.14.0"
version "1.0.2"
"@unocss/cli@0.39.3":
version "0.39.3"
@ -888,11 +886,6 @@ totalist@^3.0.0:
resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.0.tgz#4ef9c58c5f095255cdc3ff2a0a55091c57a3a1bd"
integrity sha512-eM+pCBxXO/njtF7vdFsHuqb+ElbxqtI4r5EAvk6grfAFyJ6IvWlSkfZ5T9ozC6xWw3Fj1fGoSmrl0gUs46JVIw==
type-fest@2.14.0:
version "2.14.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.14.0.tgz#f990e19169517d689c98e16d128b231022b27e12"
integrity sha512-hQnTQkFjL5ik6HF2fTAM8ycbr94UbQXK364wF930VHb0dfBJ5JBP8qwrR8TaK9zwUEk7meruo2JAUDMwvuxd/w==
ufo@^0.8.4:
version "0.8.4"
resolved "https://registry.yarnpkg.com/ufo/-/ufo-0.8.4.tgz#23e9ed82398d2116dcb378e8fba5ced8eca2ee40"

View File

@ -134,46 +134,159 @@ async function execute(
}
class EventEmitter<E extends string> {
/** @ignore */
/** @ignore */
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
private eventListeners: {
[key: string]: Array<(arg: any) => void>
} = Object.create(null)
private eventListeners: Record<E, Array<(...args: any[]) => void>> =
Object.create(null)
/** @ignore */
private addEventListener(event: string, handler: (arg: any) => void): void {
if (event in this.eventListeners) {
// eslint-disable-next-line security/detect-object-injection
this.eventListeners[event].push(handler)
} else {
// eslint-disable-next-line security/detect-object-injection
this.eventListeners[event] = [handler]
}
}
/** @ignore */
_emit(event: E, payload: any): void {
if (event in this.eventListeners) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const listeners = this.eventListeners[event as any]
for (const listener of listeners) {
listener(payload)
}
}
/**
* Alias for `emitter.on(eventName, listener)`.
*/
addListener(eventName: E, listener: (...args: any[]) => void): this {
return this.on(eventName, listener)
}
/**
* Listen to an event from the child process.
*
* @param event The event name.
* @param handler The event handler.
*
* @return The `this` instance for chained calls.
* Alias for `emitter.off(eventName, listener)`.
*/
on(event: E, handler: (arg: any) => void): EventEmitter<E> {
this.addEventListener(event, handler)
removeListener(eventName: E, listener: (...args: any[]) => void): this {
return this.off(eventName, listener)
}
/**
* Adds the `listener` function to the end of the listeners array for the
* event named `eventName`. No checks are made to see if the `listener` has
* already been added. Multiple calls passing the same combination of `eventName`and `listener` will result in the `listener` being added, and called, multiple
* times.
*
* Returns a reference to the `EventEmitter`, so that calls can be chained.
* @param eventName The name of the event.
* @param listener The callback function
*/
on(eventName: E, listener: (...args: any[]) => void): this {
if (eventName in this.eventListeners) {
// eslint-disable-next-line security/detect-object-injection
this.eventListeners[eventName].push(listener)
} else {
// eslint-disable-next-line security/detect-object-injection
this.eventListeners[eventName] = [listener]
}
return this
}
/**
* Adds a **one-time**`listener` function for the event named `eventName`. The
* next time `eventName` is triggered, this listener is removed and then invoked.
*
* Returns a reference to the `EventEmitter`, so that calls can be chained.
*
* @param eventName The name of the event.
* @param listener The callback function
*/
once(eventName: E, listener: (...args: any[]) => void): this {
const wrapper = (...args: any[]): void => {
this.removeListener(eventName, wrapper)
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
listener(...args)
}
return this.addListener(eventName, wrapper)
}
/**
* Removes the all specified listener from the listener array for the event eventName
* Returns a reference to the `EventEmitter`, so that calls can be chained.
*/
off(eventName: E, listener: (...args: any[]) => void): this {
if (eventName in this.eventListeners) {
// eslint-disable-next-line security/detect-object-injection
this.eventListeners[eventName] = this.eventListeners[eventName].filter(
(l) => l !== listener
)
}
return this
}
/**
* Removes all listeners, or those of the specified eventName.
*
* Returns a reference to the `EventEmitter`, so that calls can be chained.
*/
removeAllListeners(event?: E): this {
if (event) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete,security/detect-object-injection
delete this.eventListeners[event]
} else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
this.eventListeners = Object.create(null)
}
return this
}
/**
* @ignore
* Synchronously calls each of the listeners registered for the event named`eventName`, in the order they were registered, passing the supplied arguments
* to each.
*
* Returns `true` if the event had listeners, `false` otherwise.
*/
emit(eventName: E, ...args: any[]): boolean {
if (eventName in this.eventListeners) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,security/detect-object-injection
const listeners = this.eventListeners[eventName]
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
for (const listener of listeners) listener(...args)
return true
}
return false
}
/**
* Returns the number of listeners listening to the event named `eventName`.
*/
listenerCount(eventName: E): number {
if (eventName in this.eventListeners)
// eslint-disable-next-line security/detect-object-injection
return this.eventListeners[eventName].length
return 0
}
/**
* Adds the `listener` function to the _beginning_ of the listeners array for the
* event named `eventName`. No checks are made to see if the `listener` has
* already been added. Multiple calls passing the same combination of `eventName`and `listener` will result in the `listener` being added, and called, multiple
* times.
*
* Returns a reference to the `EventEmitter`, so that calls can be chained.
* @param eventName The name of the event.
* @param listener The callback function
*/
prependListener(eventName: E, listener: (...args: any[]) => void): this {
if (eventName in this.eventListeners) {
// eslint-disable-next-line security/detect-object-injection
this.eventListeners[eventName].unshift(listener)
} else {
// eslint-disable-next-line security/detect-object-injection
this.eventListeners[eventName] = [listener]
}
return this
}
/**
* Adds a **one-time**`listener` function for the event named `eventName` to the_beginning_ of the listeners array. The next time `eventName` is triggered, this
* listener is removed, and then invoked.
*
* Returns a reference to the `EventEmitter`, so that calls can be chained.
* @param eventName The name of the event.
* @param listener The callback function
*/
prependOnceListener(eventName: E, listener: (...args: any[]) => void): this {
const wrapper = (...args: any[]): void => {
this.removeListener(eventName, wrapper)
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
listener(...args)
}
return this.prependListener(eventName, wrapper)
}
}
class Child {
@ -311,16 +424,16 @@ class Command extends EventEmitter<'close' | 'error'> {
(event) => {
switch (event.event) {
case 'Error':
this._emit('error', event.payload)
this.emit('error', event.payload)
break
case 'Terminated':
this._emit('close', event.payload)
this.emit('close', event.payload)
break
case 'Stdout':
this.stdout._emit('data', event.payload)
this.stdout.emit('data', event.payload)
break
case 'Stderr':
this.stderr._emit('data', event.payload)
this.stderr.emit('data', event.payload)
break
}
},