diff --git a/core/signal/src/command-trigger.ts b/core/signal/src/command-trigger.ts index 4bfb9a342..80cc9850a 100644 --- a/core/signal/src/command-trigger.ts +++ b/core/signal/src/command-trigger.ts @@ -1,4 +1,4 @@ -import {requestCommand} from './core.js'; +import {requestCommand, requestCommandWithResponse} from './core.js'; import type {OmitFirstParam, Stringifyable} from '@alwatr/type'; @@ -7,16 +7,29 @@ import type {OmitFirstParam, Stringifyable} from '@alwatr/type'; */ export const commandTrigger = { /** - * Dispatch request command signal with commandArgument as detail and return untilNext of callback signal. + * Dispatch request command signal with commandArgument as detail. * * Example: * * ```ts - * const returnObject = await commandTrigger.request('show-dialog', {foo: 'bar'}); + * commandTrigger.request('show-dialog', {foo: 'bar'}); * ``` */ request: requestCommand, + /** + * Dispatch request command signal with commandArgument as detail and return untilNext of callback signal. + * + * Request command and wait for answer. + * + * Example: + * + * ```ts + * const response = await commandTrigger.requestWithResponse('show-dialog', {foo: 'bar'}); + * ``` + */ + requestWithResponse: requestCommandWithResponse, + /** * Bind define command to special command. * @@ -38,10 +51,22 @@ export const commandTrigger = { * Example: * * ```ts - * const returnObject = await showDialog.request({foo: 'bar'}); + * showDialog.request({foo: 'bar'}); * ``` */ request: requestCommand.bind(null, commandId) as unknown as - OmitFirstParam>, + OmitFirstParam>, + + /** + * Dispatch request command signal with commandArgument as detail and return untilNext of callback signal. + * + * Example: + * + * ```ts + * const response = await showDialog.requestWithResponse({foo: 'bar'}); + * ``` + */ + requestWithResponse: requestCommandWithResponse.bind(null, commandId) as unknown as + OmitFirstParam>, }), } as const; diff --git a/core/signal/src/core.ts b/core/signal/src/core.ts index 7c7f9423d..ce6a9b73c 100644 --- a/core/signal/src/core.ts +++ b/core/signal/src/core.ts @@ -340,24 +340,27 @@ export const setContextProvider = , TReturn extends Stringifyable>( signalId: string, - signalProvider: ProviderFunction, + signalProvider: ProviderFunction, options: Partial> = {}, ): void => { options.debounce ??= 'AnimationFrame'; logger.logMethodArgs('defineCommand', {commandId: signalId, options}); const requestSignalId = 'request-' + signalId; removeAllListeners(requestSignalId); - subscribe( + subscribe( requestSignalId, async (argumentObject) => { - const callbackSignalId = argumentObject._callbackSignalId; - // TODO: validate callbackSignalId - const commandReturn = await signalProvider(argumentObject); - dispatch(callbackSignalId, commandReturn, {debounce: options.debounce}); - }, - { - receivePrevious: 'No', // Prevent to merge multiple previous requests + clearDetail(requestSignalId); // clean argumentObject from memory + if (argumentObject._callbackSignalId == null) { + signalProvider(argumentObject); + } + else { + dispatch(argumentObject._callbackSignalId, await signalProvider(argumentObject), { + debounce: options.debounce, + }); + } }, + {receivePrevious: 'NextCycle'}, ); }; @@ -380,34 +383,59 @@ export const requestContext = ( return dispatch(contextId, requestParam, options); }; +/** + * Dispatch request command signal with commandArgument as detail. + * + * Example: + * + * ```ts + * requestCommand('show-dialog', {foo: 'bar'}); + * ``` + */ +export const requestCommand = >( + commandId: string, + commandArgument: TArgument, +): void => { + logger.logMethodArgs('requestCommand', {commandId, commandArgument}); + dispatch(`request-${commandId}`, commandArgument, {debounce: 'No'}); +}; + /** * Dispatch request command signal with commandArgument as detail and return untilNext of callback signal. * + * Request command and wait for answer. + * * Example: * * ```ts - * const returnObject = await requestCommand('show-dialog', {foo: 'bar'}); + * const response = await requestCommandWithResponse('show-dialog', {foo: 'bar'}); * ``` */ -export const requestCommand = , TReturn extends Stringifyable>( +export const requestCommandWithResponse = async < + TArgument extends Record, + TReturn extends Stringifyable +>( commandId: string, commandArgument: TArgument, ): Promise => { - return new Promise((resolve) => { - logger.logMethodArgs('requestCommand', {commandId, commandArgument}); + logger.logMethodArgs('requestCommand', {commandId, commandArgument}); - const requestSignalId = `request-${commandId}`; - const callbackSignalId = `callback-${commandId}-${++_lastListenerAutoId}`; + const _requestSignalId = `request-${commandId}`; + const _callbackSignalId = `callback-${commandId}-${++_lastListenerAutoId}`; + const untilCallback = untilNext(_callbackSignalId); - subscribe(callbackSignalId, resolve, {once: true, priority: true, receivePrevious: 'No'}); - // TODO: refactor _untilNextSignal with option and use it + dispatch( + _requestSignalId, + { + ...commandArgument, + _callbackSignalId, + }, + {debounce: 'No'}, + ); - dispatch( - requestSignalId, - {...commandArgument, _callbackSignalId: callbackSignalId}, - {debounce: 'No'}, - ); - }); + const response = await untilCallback; + destroySignal(_callbackSignalId); + return response; }; /** @@ -426,3 +454,22 @@ export const clearDetail = (signalId: string): void => { const signal = getSignalObject(signalId); delete signal.detail; }; + +/** + * Delete signal object with detail and listeners and options. + * + * new subscriber options.receivePrevious not work until new signal + * + * Example: + * + * ```ts + * destroySignal('product-list'); + * ``` + */ +export const destroySignal = (signalId: string): void => { + logger.logMethodArgs('destroySignal', signalId); + const signal = _signalStorage[signalId]; + if (signal == null) return; + signal.listenerList.length = 0; + delete _signalStorage[signalId]; +};