如何在React(async/await)中创建原子进程



想象一下,当按下按钮时,可以点赞一篇帖子。这个按钮可以修改远程数据库,因此需要一段时间才能将类似内容与特定的帖子关联起来。

现在,如果用户开始快速按下按钮,代码为:

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");
};

这样一切都会好起来,但如果用户快速按下按钮,在上面代码中描述的所有过程完成之前,他将无法看到GUI的变化(类似按钮的颜色(如果喜欢则为红色,如果不喜欢则为白色(。

我还想过制作一个反抖动函数(用于handlePress(,如下所示:

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 && !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

但有了这个,我唯一能做的就是减少得到混乱结果的机会。也就是说,我仍然有与第一个代码相同的问题。

有什么想法可以做我想做的事情,这样我得到的结果是有序的,避免了写入数据库的等待时间?

谢谢。

解决方案是立即禁用该按钮。使用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");
};

@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;
});
}

最新更新