Динамический инжект кода в redux-saga

Недостаток всей инфраструктуры redux в том, что редюсеры и middlewares фиксировано компонуются при инициализации приложения и сложно динамически подключать новые саги и редюсеры в runtime.

Сложно, но не невозможно.

Если вы спросите сагиста как динамически добавить сагу он скажет `Use store.runSaga()`. И будет прав, но лишь частично.

На самом деле есть другой способ, который не требует таскать за собой в фабрики и контейнеры сам store.

В способе, который я придумал, понадобиться только один раз подключать специальную сагу, через которую мы будем внедрять функции на выполнение.

И эта универсальная корневая сага представляет из себя цикличного слушателя некоторого внешнего интерфейса.

function* rootSaga() {
while (true) {
const subject = yield cps(awaitRun);
yield fork(subject);
}
}

Мы создаем бесконечный цикл, в котором мы ожидаем ответа функции awaitRun. Внутри awaitRun происходит ожидание поступления новой функции через специальную функцию, объявленную во внешнем скоуп.

Здесь намеренно используется cps, вместо call, потому что call реализует асинхронность через Promise. А любой Promise отрабатывает с задержкой, за время которого мы можем пропустить следующие запросы. Эффектcps же работает без задержек, поэтому его использование принципиально важно.

Теперь можно создать точку входа в сагу. Такой точкой для нас будет внешняя функция run . Она объявляется как переменная во внешнем scope вместе с константойrunQueue.

const runQueue = [];
let run = (task) => {
runQueue.push(task);
};

runQueue — будет содержать очередь функций на выполнение. run — будет добавлять функции в runQueue.awaitRun — это функция, которая будет динамически забирать из runQueue функции и передавать на выполнение.

function awaitRun(cb) {
if (runQueue.length > 0) {
cb(null, runQueue.shift());
} else {
run = (action) => {
run = false;
cb(null, action);
};
}
}

Может случится так, что мы начнем передавать в сагу функции раньше, чем она будет запущена, поэтому runQueue нам нужен, что бы они не пришли мимо.

Когда же функции в очереди закончатся, runQueue нам больше будет не нужен и мы начнем выполнять monkey-patching для самой функцииrun, подменяя ее на обертку для cb , тем самым получая требуемый результат незамедлительно.

Через функцию run можно выполнить любой код внутри саги, в том числе запускать новые экшены или подключать новые саги.

run(function* () {
yield take('CONNECT_NEW_SAGA');
yield put({type: 'HERE_COMES_NEW_SAGA'});
yield myNewSaga();
});

Вероятно, доработав функцию run, можно так же добиться возможности получать асинхронный ответ от результата выполнения функции/генератора.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.