如何防止/锁定函数返回,直到另一个单独的异步调用得到解决



我正在进行路由身份验证,并将身份验证状态存储在上下文中,以便其他React组件可以检查用户是否登录

const [loggedInUser, setLoggedInUser] = useState(null)
const authed = () => !!loggedInUser
useEffect(() => {
async function fetchData() {
const response = await fetch('/loggedInUser')
.then(res => res.json())
setLoggedInUser(response)
}
fetchData()
}, [])

对此代码的快速解释是,我需要在代码的各个部分中包含用户数据(如id(,因此我需要存储loggedInUser对象。然而,对于更简单的任务,例如检查用户是否登录,我使用函数authed来检查loggedInUser变量是否包含对象(用户已登录(或为null。

为了检查用户是否登录,我使用passport.js,我的'/loggedInUser'路由如下所示:

app.get('/loggedInUser', async (req, res) => {
if (!req.isAuthenticated())
return null
const { id, name } = await req.user
.then(res => res.dataValues)
return res.json({ id, name })
})

问题是,在useEffect()fetch()能够到达GET路由并使用对setLoggedInUser(response)的API响应之前,消耗该上下文并检查authed()的代码正在运行。所以authed()的使用总是返回false,即使API响应后来将loggedInUser设置为某个对象值,而现在authed()为true。很明显,这是一个竞赛条件,但我不确定如何解决它。

有没有一个优雅的解决方案,在useEffect((完全"设置"loggedInUser的状态之前,我可以"锁定"authed()不返回值?

我设想的一个(糟糕的(解决方案可能是这样的:

const [loggedInUser, setLoggedInUser] = useState(null)
const isFinishedLoading = false // <--
function authed() {
while (!isFinishedLoading) {} // a crude lock
return !!loggedInUser
}
useEffect(() => {
async function fetchData() {
const response = await fetch('/loggedInUser')
.then(res => res.json())
setLoggedInUser(response)

isFinishedLoading = true // <--
}
fetchData()
}, [])

是否有更好的方法来"锁定"函数authed(),直到加载完成?


编辑:

为了澄清我在评论中对authed()的使用,这里是我的App.js的精简版本

export default function App() {
return (
<>
<AuthProvider>
<Router className="Router">
<ProtectedRoute path="/" component={SubmittalTable} />
<Login path="/login" />
</Router>
</AuthProvider>
</>
)
}
function ProtectedRoute({ component: Component, ...rest }){
const { authed } = useContext(AuthContext)
if (!authed()) // due to race condition, authed() is always false
return (<Redirect from="" to="login" noThrow />)
return (<Component {...rest} />)
}

我不明白为什么需要authed()作为函数,而不直接使用isLoggedIn。此外,我看不出您正在设置上下文值,但无论如何。。。

一般性建议

一般来说:在React中,试着思考

  • "我的应用程序在任何给定时刻的状态是什么
  • 而不是";应该按照哪个顺序发生什么

在您的情况下:

  • "基于状态">
  • 而不是";一旦发生事情就重定向">

用户被授权未被授权在任何特定时刻使用您的应用程序。这是一个状态,您可以将此状态存储在某个地方。让我们把这种状态称为isAuthorized

存储状态的不同位置

  1. 您可以将isAuthorized存储在Context中,只要您知道它在需要时可用。如果上下文在您想知道用户是否获得授权时不可用(在您的应用程序中似乎是这样(,那么您就不能使用上下文来存储isAuthorized(至少不是单独使用(。

  2. 您可以在每次需要时提取isAuthorized。在提取响应之前,isAuthorized不可用。你的应用程序现在的状态如何?可能是notReady。您可以将状态notReady存储在某个位置,例如再次存储在上下文中。(notReady最初总是true,所以只有明确表示应用程序已准备就绪。(只要是notReady,应用程序可能会显示Spinner,而不执行任何其他操作。

  3. 您可以将isAuthorized存储在浏览器存储中(例如sessionStorage(。它们可以跨页面加载,因此不必每次都获取状态。浏览器存储应该是同步的,但事实上,我会把它们视为异步的,因为我读到的关于浏览器存储的东西并不能激发人们的信心。

问题及解决方案

您要做的是将isAuthorized存储在(1(上下文中,并(2(每次提取它,因此您有两个状态,需要同步。无论如何,确实需要在一开始至少获取isAuthorized一次,否则,应用程序就无法使用。因此,您确实需要同步和状态(3(notReady(或isReady(。

使用React中的useEffect(或"依赖项"(完成同步状态,例如:

useEffect(() => {
setIsFinishedLoading( false );    // state (3) "app is ready"
fetchData().then( response => {
setLoggedInUser( response );    // state (2) "isAuthorized" from fetch
setIsFinishedLoading( true );
}).catch( error => {
setLoggedInUser( null );
setIsFinishedLoading( true );
});
}, []);
useEffect(() => {
if( isFinishedLoading ){
setIsAuthorized( !!response );      // state (1) "isAuthorized" in local state or Context
}

}, [ isFinishedLoading, response ]);

"阻塞">

你可能根本不需要它,但是:

在Javascript中,阻止这种意义上的代码执行是不可能的。相反,您将在其他时间执行代码,例如使用Promises。这再次需要以稍微不同的方式思考。

你不能做:

function authed(){
blockExecutionBasedOnCondition
return !!loggedInUser
}

但你可以做到:

function authed(){
return !!loggedInUser;
}
function executeAuthed(){
someConditionWithPromise.then( result => {
authed();
});
}

相关内容

  • 没有找到相关文章

最新更新