Skip to content

@ackee/antonio-utils: Handler for a same requestId cancelled even for the incoming request #85

@jancama2

Description

@jancama2

Problem:
There is a problem with takeLatestRequest

Version:
@ackee/antonio-utils@4.0.11

Steps to reproduce:

  • dispatch a new action for a new request
  • dispatch a next action with the same requestId
  • both the previous handler and the handler for the next action are cancelled

A bit different yet working solution, might take an inspiration:

import type { AnyAction } from 'redux';
import type { Task } from 'redux-saga';

import { call, cancel, cancelled, fork, put, take } from 'redux-saga/effects';

export interface ActionCreator<Type extends string = string> {
    (...args: any[]): {
        type: Type;
    };
    toString: () => Type;
}

export function takeLatestRequest<R extends ActionCreator>(
    {
        actionCreator,
        cancelActionFunction,
        idSelector,
    }: {
        actionCreator: R;
        idSelector: (action: ReturnType<R>) => string;
        cancelActionFunction: (action: ReturnType<R>) => AnyAction;
    },
    handler: (action: ReturnType<R>, signal: AbortSignal) => void,
) {
    const tasks = new Map<string, Task>();

    return fork(function* () {
        try {
            while (true) {
                const action: ReturnType<R> = yield take(actionCreator.toString());

                const id = idSelector(action);

                if (tasks.has(id)) {
                    yield cancel(tasks.get(id));

                    tasks.delete(id);
                }

                const task: Task = yield fork(function* () {
                    const controller = new AbortController();

                    try {
                        yield call(handler, action, controller.signal);
                    } finally {
                        if (yield cancelled()) {
                            controller.abort();

                            yield put(cancelActionFunction(action));
                        }
                    }
                });

                task.toPromise().then(() => {
                    if (tasks.has(id)) {
                        tasks.delete(id);
                    }
                });

                tasks.set(id, task);
            }
        } finally {
            if (yield cancelled()) {
                for (const [, task] of tasks.entries()) {
                    yield cancel(task);
                }
            }
        }
    });
}

Usage:

export default function* () {
    yield takeLatestRequest(
        {
            actionCreator: updateSecret,
            cancelActionFunction: action => updateSecretReset(action.meta.id),
            idSelector: action => action.meta.id,
        },
        handler,
    );
}

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions