Очередь асинхронных действий в рефлюксе
При использовании хранилищ RefluxJS с асинхронными действиями вы можете легко получить условия гонки между вашими действиями.
Абстрактное описание проблемы
Например, наше хранилище находится в состоянии X. Асинхронное действие A вызывается из X, и до его завершения вызывается другое асинхронное действие B, также из X. Отсюда, независимо от того, какое действие заканчивается первым, оно идет не так.
- B заканчивается первым с состоянием Y1, A заканчивается последним и перезаписывает состояние Y1 с помощью Y2.
- A заканчивается первым с состоянием Y2, B перезаписывает Y2 с Y1.
Желаемое поведение будет иметь:
A B
X -> Y -> Z
Где B основан не на X, а на Y, и приводит к согласованному состоянию Z, вместо двух действий, основанных на одном и том же состоянии, приводящих к несогласованному состоянию:
A
X -> Y1 .--> Y2
\ /
'----'
B
Реализован пример проблемы
Я написал минимальный рабочий пример, работающий с Node, о проблеме, о которой я говорю.
var Q = require('q');
var Reflux = require('reflux');
var RefluxPromise = require('reflux-promise');
Reflux.use(RefluxPromise(Q.Promise));
var AsyncActions = Reflux.createActions({
'add': { asyncResult: true }
});
var AsyncStore = Reflux.createStore({
init: function () {
// The state
this.counter = 0;
AsyncActions.add.listenAndPromise(this.onAdd, this);
},
// Increment counter after a delay
onAdd: function(n, delay) {
var that = this;
return apiAdd(this.counter, n, delay)
.then(function (newCounter) {
that.counter = newCounter;
that.trigger(that.counter);
});
}
});
// Simulate an API call, that makes the add computation. The delay
// parameter is used for testing.
// @return {Promise<Number>}
function apiAdd(counter, n, delay) {
var result = Q.defer();
setTimeout(function () {
result.resolve(counter + n);
}, delay);
return result.promise;
}
// Log the store triggers
AsyncStore.listen(console.log.bind(undefined, 'Triggered'));
// Add 3 after 1 seconds.
AsyncActions.add(3, 1000);
// Add 100 almost immediately
AsyncActions.add(100, 1);
// Console output:
// > Triggered 100
// > Triggered 3
// Desired output (queued actions):
// > Triggered 3
// > Triggered 103
С этими зависимостями в package.json
{
"dependencies": {
"q": "^1.3.0",
"reflux": "^0.3",
"reflux-promise": "^1"
}
}
Суть вопроса
Я ожидал, что RefluxJS поставит в очередь действия, но это не так. Поэтому я ищу способ правильно заказать эти действия. Но даже если мне удалось каким-то образом поставить в очередь эти действия (так что B выдается после A), как я могу быть уверен, что, когда A завершится, выдача B все еще будет действительным действием? Возможно, я использую RefluxJS неправильно, во-первых, и этот сценарий не происходит в правильно структурированном приложении.
Является ли решение очереди асинхронных действий (при условии, что это возможно в приложении Reflux) решением? Или мы должны как-то избегать этих сценариев?
1 ответ
Ваш пример кажется скорее проблемой с понятием "источник правды", чем что-либо еще. Вы сохраняете текущее состояние номера ТОЛЬКО на стороне клиента, но ТОЛЬКО обновляете его после получения подтверждения со стороны сервера о выполняемой над ним операции.
Конечно, это создаст проблемы. Вы смешиваете действия над числом и сохранением числа странным образом, где нет единого источника правды о том, что это за число в данный момент. Он находится в подвешенном состоянии между временем, когда действие называется законченным... и это не хорошо.
Либо сохраните номер на стороне клиента, и каждый раз, когда вы добавляете к нему, добавляете к этому номеру напрямую, а затем сообщаете серверной стороне, что такое новый номер... (т.е. сторона клиента берет на себя ответственность как источник правды для номера пока работает клиентская часть)
ИЛИ сохраните сервер нумерации, и каждый раз, когда вы выполняете действие с клиентской стороны, сервер возвращает новый обновленный номер. (т.е. источник правды для числа полностью на стороне сервера).
Затем, даже если возникают расы, у вас все еще есть источник правды для того, что это за число, и этот источник можно проверить и подтвердить. Например, если на стороне сервера хранится источник правды для числа, то API также может возвращать отметку времени для состояния этого значения каждый раз, когда оно возвращает его, и вы можете сравнить его с последним значением, которое вы получили от API для убедитесь, что вы на самом деле используете новейшее значение.