#javascript #reactjs #react-native #asynchronous #async-await
#javascript #reactjs #react-native #асинхронный #async-await
Вопрос:
Представьте пост, который может понравиться при нажатии кнопки. Эта кнопка изменяет удаленную базу данных, поэтому потребуется некоторое время, чтобы связать like с конкретным сообщением.
Теперь, если пользователь начинает так быстро нажимать кнопку с помощью этого кода:
state = {
isLiked: false,
}
handlePress = () => {
this.setState(
{
isLiked: !this.state.isLiked,
},
this.handleLike
);
};
handleLike = async () => {
const { postId } = this.props;
try {
console.log(isLiked ? "Liking" : "Disliking")
await db.processLike(postId);
} catch (err) {
// If an error has occurred, reverse the 'isLiked' state
this.setState({
isLiked: !this.state.isLiked,
});
// TODO - Alert the error to the user in a toast
console.log(err);
}
console.log("DONE");
};
Поскольку все асинхронно, можно увидеть эту ситуацию:
Нравится
Не нравится
ГОТОВО <———- Нелюбовь к сделанному
ГОТОВО <———- Симпатия сделана
Я подумал создать состояние «isLiking», чтобы избежать запуска кода до завершения всего асинхронного задания. Что-то вроде этого:
state = {
isLiking: false,
isLiked: false,
}
handlePress = () => {
if (this.state.isLiking) return; <------------------------------------
this.setState(
{
isLiking: true, <------------------------------------
isLiked: !this.state.isLiked,
},
this.handleLike
);
};
handleLike = async () => {
const { postId } = this.props;
try {
console.log(isLiked ? "Liking" : "Disliking");
await db.processLike(postId);
} catch (err) {
// If an error has occurred, reverse the 'isLiked' state
this.setState({
isLiked: !this.state.isLiked,
});
// TODO - Alert the error to the user in a toast
console.log(err);
}
this.setState({ isLiking: false }); <------------------------------------
console.log("DONE");
};
С этим все идет нормально, но если пользователь быстро нажмет кнопку, он не сможет увидеть изменения в графическом интерфейсе (цвет кнопки «Нравится» (красный, если нравится, белый, если нет)), пока не завершится весь процесс, описанный в приведенном выше коде.
Я также подумал о том, чтобы создать деактивированную функцию (для ручного нажатия) следующим образом:
export const debounce = (func, wait, immediate) => {
/*
Returns a function, that, as long as it continues to be invoked, will not
be triggered. The function will be called after it stops being called for
N milliseconds. If `immediate` is passed, trigger the function on the
leading edge, instead of the trailing.
*/
let timeout;
return function () {
let context = this,
args = arguments;
let later = function () {
timeout = null;
if (!immediate) func.apply(context, args);
};
let callNow = immediate amp;amp; !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
...
debuncedHandlePress = debounce(this.handlePress, 500); // Now, when the button is pressed, it will call this function, instead of the original handlePress
Но при этом единственное, что я делаю, это уменьшаю вероятность получения беспорядочных результатов. То есть у меня все та же проблема, что и с первым кодом.
Есть идеи делать то, что я хочу, таким образом, чтобы результаты, которые я получаю, упорядочивались и избегали небольшого ожидания записи в базу данных?
Спасибо.
Ответ №1:
Решение состоит в том, чтобы немедленно отключить кнопку. Используя setState
, вы не можете ожидать немедленного обновления isLinking
, и именно поэтому вы раздражены. Одним из решений является использование flag variable
вместо использования state
.
Вы можете исправить это таким образом.
state = {
isLiked: false,
}
constructor(props) {
this.isLiking = false; <------------------------------------
}
handlePress = () => {
this.isLiking = true; <------------------------------------
this.setState(
{
isLiked: !this.state.isLiked,
},
this.handleLike
);
};
handleLike = async () => {
const { postId } = this.props;
try {
console.log(isLiked ? "Liking" : "Disliking");
await db.processLike(postId);
} catch (err) {
// If an error has occurred, reverse the 'isLiked' state
this.setState({
isLiked: !this.state.isLiked,
});
// TODO - Alert the error to the user in a toast
console.log(err);
}
this.isLiking = false; <------------------------------------
console.log("DONE");
};
Ответ №2:
Ответ @Prime работает, но он не работает, когда у вас есть действия, которые разбросаны по всему приложению, и было бы сложно все синхронизировать.
В моем случае это было обновление токена API. Поскольку запросы API разбросаны по всему приложению, заблокировать вызов с помощью переменной состояния практически невозможно.
Поэтому я представляю другое решение:
/*
The long running operation
*/
const myLongRunningOperation = async () => {
// Do an API call, for instance
}
/*
Promise locking-queueing structure
*/
var promiesCallbacks = [];
const resolveQueue = value => {
promiesCallbacks.forEach(x => x.resolve(value));
promiesCallbacks = [];
};
const rejectQueue = value => {
promiesCallbacks.forEach(x => x.reject(value));
promiesCallbacks = [];
};
const enqueuePromise = () => {
return new Promise((resolve, reject) => {
promiesCallbacks.push({resolve, reject});
});
};
/*
The atomic function!
*/
var actionInProgress = false;
const doAtomicAction = () => {
if (actionInProgress) {
return enqueuePromise();
}
actionInProgress = true;
return myLongRunningOperation()
.then(({ access }) => {
resolveQueue(access);
return access;
})
.catch((error) => {
rejectQueue(error);
throw error;
})
.finally(() => {
actionInProgress = false;
});
}