Skip to content

Commit

Permalink
🏷️ Add proper typing for takeRequest & takeLatestRequest utils
Browse files Browse the repository at this point in the history
  • Loading branch information
cermakjiri committed Oct 19, 2021
1 parent 7acce57 commit be658bc
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 32 deletions.
8 changes: 4 additions & 4 deletions packages/@ackee/antonio-utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ yarn add @ackee/antonio-utils -S

#### Parameters

- `actionTypes: Object`
- `actionTypes: TakeRequest`
- `REQUEST: ActionPattern` - action type that launches the saga
- `CANCEL: ActionPattern` - action type that aborts the running saga
- `saga(requestAction, signal: Signal): Function` - the actual API request is made here
Expand Down Expand Up @@ -65,11 +65,11 @@ export default function* () {

#### Parameters

- `params: TakeLatestRequest`
- `REQUEST: ActionPattern` - action type that launches the saga
- `params: TakeLatestRequest<RequestAction extends AnyAction, CancelAction extends AnyAction>`
- `REQUEST: RequestAction['type']` - action type that launches the saga
- `cancelTask<A extends AnyAction = AnyAction>(requestId: RequestId, action: A): A;` - Redux action that will cancel the
running saga
- `requestIdSelector?<A extends AnyAction = AnyAction>(action: A): RequestId;` - A function that receives request action as 1st arg. and returns unique ID of this action, e.g. user ID.
- `cancelTask(requestId: RequestId, action: RequestAction): CancelAction;` - A function that receives request action as 1st arg. and returns unique ID of this action, e.g. user ID.
- `saga(requestAction, signal: Signal): Function` - the actual API request is made here

#### Example
Expand Down
29 changes: 18 additions & 11 deletions packages/@ackee/antonio-utils/src/saga-effects/takeLatestRequest.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,37 @@
import type { AnyAction } from 'redux';
import { takeEvery, put, spawn } from 'redux-saga/effects';

import cancellableHandler from './utils/cancellableHandler';

import type { TakeLatestRequest, RequestHandler, RequestId } from '../types';

export default function* takeLatestRequest(
{ REQUEST, cancelTask, requestIdSelector }: TakeLatestRequest,
requestHandler: RequestHandler,
export default function* takeLatestRequest<
RequestAction extends AnyAction = AnyAction,
CancelAction extends AnyAction = AnyAction,
>(
{ REQUEST, cancelTask, requestIdSelector }: TakeLatestRequest<RequestAction, CancelAction>,
requestHandler: RequestHandler<RequestAction>,
) {
const runningTasks = new Set<RequestId>();
const DEFAULT_REQUEST_ID = Symbol('DEFAULT_REQUEST_ID');

yield takeEvery(REQUEST, function* (action) {
yield takeEvery(REQUEST, function* (action: RequestAction) {
const requestId = requestIdSelector ? requestIdSelector(action) : DEFAULT_REQUEST_ID;

if (runningTasks.has(requestId)) {
yield put(cancelTask(requestId, action));
runningTasks.delete(requestId);
}

yield spawn(cancellableHandler, {
handler: requestHandler,
handlerArg: action,
CANCEL: cancelTask(requestId, action).type,
onComplete() {
runningTasks.delete(requestId);
},
yield spawn(function* () {
yield* cancellableHandler<RequestAction, CancelAction['type']>({
handler: requestHandler,
handlerArg: action,
CANCEL: cancelTask(requestId, action).type,
onComplete() {
runningTasks.delete(requestId);
},
});
});

runningTasks.add(requestId);
Expand Down
8 changes: 6 additions & 2 deletions packages/@ackee/antonio-utils/src/saga-effects/takeRequest.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import type { AnyAction } from 'redux';
import { take } from 'redux-saga/effects';
import type { RequestHandler, TakeRequest } from '../types';
import cancellableHandler from './utils/cancellableHandler';

/**
* Blocking custom saga effect that can cancel the API request
*/
export default function* takeRequest(actionTypes: TakeRequest, handler: RequestHandler) {
export default function* takeRequest<RequestAction extends AnyAction = AnyAction>(
actionTypes: TakeRequest,
handler: RequestHandler<RequestAction>,
) {
while (true) {
/* @ts-ignore */
const action = yield take(actionTypes.REQUEST);

yield cancellableHandler({
yield cancellableHandler<RequestAction, typeof actionTypes['CANCEL']>({
handler,
handlerArg: action,
CANCEL: actionTypes.CANCEL,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import type { AnyAction } from 'redux';
import { call, take, race } from 'redux-saga/effects';
import { CancellableHandler } from '../../types';
import type { ActionPattern } from 'redux-saga/effects';
import type { CancellableHandler } from '../../types';

if (!('AbortController' in window)) {
require('abortcontroller-polyfill/dist/abortcontroller-polyfill-only.js');
}

const noop = function* () {};

export default function* cancellableHandler({ handlerArg, CANCEL, handler, onComplete = noop }: CancellableHandler) {
export default function* cancellableHandler<RequestAction extends AnyAction, CancelActionType extends ActionPattern>({
handlerArg,
CANCEL,
handler,
onComplete = noop,
}: CancellableHandler<RequestAction, CancelActionType>) {
const controller = new AbortController();

function* tasks() {
Expand Down
24 changes: 11 additions & 13 deletions packages/@ackee/antonio-utils/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
import { AnyAction } from 'redux';
import type { AnyAction } from 'redux';
import type { ActionPattern } from 'redux-saga/effects';

type Fn = (...args: any[]) => any;
export type RequestHandler<RequestAction> = (requestAction: RequestAction, signal: AbortSignal) => any;

export type RequestHandler<A extends AnyAction = AnyAction> = (requestAction: A, signal: AbortSignal) => any;

export interface CancellableHandler {
handlerArg: any;
CANCEL: ActionPattern;
handler: RequestHandler;
onComplete?: Fn;
export interface CancellableHandler<RequestAction extends AnyAction, CancelActionType = AnyAction['type']> {
handlerArg: RequestAction;
CANCEL: CancelActionType;
handler: RequestHandler<RequestAction>;
onComplete?(...args: any[]): void;
}

export type RequestId = symbol | string | number;

export interface TakeLatestRequest {
REQUEST: ActionPattern;
cancelTask<A extends AnyAction = AnyAction>(requestId: RequestId, action: A): A;
requestIdSelector?<A extends AnyAction = AnyAction>(action: A): RequestId;
export interface TakeLatestRequest<RequestAction extends AnyAction, CancelAction extends AnyAction> {
REQUEST: RequestAction['type'];
cancelTask(requestId: RequestId, action: RequestAction): CancelAction;
requestIdSelector?(action: RequestAction): RequestId;
}

export interface TakeRequest {
Expand Down

0 comments on commit be658bc

Please sign in to comment.